理解Tomcat的WebappClassLoader(web應用類加載器)


我目前的系統可能需要自己實現類加載器,想要參考Tomcat的實現。關於Tomcat的類加載機制,網上文章很多,當然大多都是互相copy,有價值的信息並不多,不得已我開始看Tomcat代碼,略有所得,記錄起來。主要針對WebappClassLoader。
 
負責Web應用的類加載的是org.apache.catalina.loader.WebappClassLoader,它幾個比較重要的方 法:findClass(),loadClass(),findClassInternal(),findResourceInternal().類加載 器被用來加載一個類的時候,loadClass()會被調用,loadClass()則調用findClass()。后兩個方法是 WebappClassLoader的私有方法,findClass()調用findClassInternal()來創建class對象,而 findClassInternal()則需要findResourceInternal()來查找.class文件。
 
通常自己實現類記載器只要實現findclass即可,這里為了實現特殊目的而override了loadClass().
 
下面是精簡過的代碼(去除了幾乎全部關於log、異常和安全控制的代碼):
 
findClass:
 1 public Class findClass(String name) throws ClassNotFoundException {
2 // 先試圖自己加載類,找不到則請求parent來加載
3 // 注意這點和java默認的雙親委托模式不同
4 Class clazz = null;
5 clazz = findClassInternal(name);
6 if ((clazz == null) && hasExternalRepositories) {
7 synchronized (this) {
8 clazz = super.findClass(name);
9 }
10 }
11 if (clazz == null) {
12 throw new ClassNotFoundException(name);
13 }
14
15 return (clazz);
16 }
loadClass:
 1 public Class loadClass(String name, boolean resolve)
2 throws ClassNotFoundException {
3 Class clazz = null;
4 // (0) 先從自己的緩存中查找,有則返回,無則繼續
5 clazz = findLoadedClass0(name);
6 if (clazz != null) {
7 if (resolve) resolveClass(clazz);
8 return (clazz);
9 }
10 // (0.1) 再從parent的緩存中查找
11 clazz = findLoadedClass(name);
12 if (clazz != null) {
13 if (resolve) resolveClass(clazz);
14 return (clazz);
15 }
16 // (0.2) 緩存中沒有,則首先使用system類加載器來加載
17 clazz = system.loadClass(name);
18 if (clazz != null) {
19 if (resolve) resolveClass(clazz);
20 return (clazz);
21 }
22 //判斷是否需要先讓parent代理
23 boolean delegateLoad = delegate || filter(name);
24 // (1) 先讓parent加載,通常delegateLoad == false,即這一步不會執行
25
26 if (delegateLoad) {
27 ClassLoader loader = parent;
28 if (loader == null)
29 loader = system;
30 clazz = loader.loadClass(name);
31 if (clazz != null) {
32 if (resolve) resolveClass(clazz);
33 return (clazz);
34 }
35 }
36 // (2) delegateLoad == false 或者 parent加載失敗,調用自身的加載機制
37 clazz = findClass(name);
38 if (clazz != null) {
39 if (resolve) resolveClass(clazz);
40 return (clazz);
41 }
42 // (3) 自己加載失敗,則請求parent代理加載
43
44 if (!delegateLoad) {
45 ClassLoader loader = parent;
46 if (loader == null)
47 loader = system;
48 clazz = loader.loadClass(name);
49 if (clazz != null) {
50 return (clazz);
51 }
52 }
53 throw new ClassNotFoundException(name);
54 }
findClassInternal:
 1 protected Class findClassInternal(String name)
2 throws ClassNotFoundException {
3 if (!validate(name))
4 throw new ClassNotFoundException(name);
5 //根據類名查找資源
6 String tempPath = name.replace('.', '/');
7 String classPath = tempPath + ".class";
8 ResourceEntry entry = null;
9 entry = findResourceInternal(name, classPath);
10
11 if (entry == null)
12 throw new ClassNotFoundException(name);
13 //如果以前已經加載成功過這個類,直接返回
14
15 Class clazz = entry.loadedClass;
16 if (clazz != null)
17 return clazz;
18 //以下根據找到的資源(.class文件)進行:1、定義package;2、對package安全檢查;3、定義class,即創建class對象
19 synchronized (this) {
20 if (entry.binaryContent == null && entry.loadedClass == null)
21 throw new ClassNotFoundException(name);
22 // Looking up the package
23 String packageName = null;
24 int pos = name.lastIndexOf('.');
25 if (pos != -1)
26 packageName = name.substring(0, pos);
27 Package pkg = null;
28 if (packageName != null) {
29 pkg = getPackage(packageName);
30 // Define the package (if null)
31 if (pkg == null) {
32 //定義package的操作,此處省略,具體參看源碼
33 pkg = getPackage(packageName);
34 }
35 }
36 if (securityManager != null) {
37 //安全檢查操作,此處省略,具體參看源碼
38 }
39 //創建class對象並返回
40 if (entry.loadedClass == null) {
41 try {
42 clazz = defineClass(name, entry.binaryContent, 0,
43 entry.binaryContent.length,
44 new CodeSource(entry.codeBase, entry.certificates));
45 } catch (UnsupportedClassVersionError ucve) {
46 throw new UnsupportedClassVersionError(
47 ucve.getLocalizedMessage() + " " +
48 sm.getString("webappClassLoader.wrongVersion",
49 name));
50 }
51 entry.loadedClass = clazz;
52 entry.binaryContent = null;
53 entry.source = null;
54 entry.codeBase = null;
55 entry.manifest = null;
56 entry.certificates = null;
57 } else {
58 clazz = entry.loadedClass;
59 }
60 }
61 return clazz;
62 }

findResouceInternal():

 

下幾篇介紹WebappLoader,StandardContext,StandardWrapper,ApplicationDispatcher在類加載中的作用。其中ApplicationDispatcher是核心。

  1   //要先加載相關實體資源(.jar) 再加載查找的資源本身
2 protected ResourceEntry findResourceInternal(String name, String path) {
3 //先根據類名從緩存中查找對應資源 ,有則直接返回
4 ResourceEntry entry = (ResourceEntry) resourceEntries.get(name);
5 if (entry != null)
6 return entry;
7 int contentLength = -1;//資源二進制數據長度
8 InputStream binaryStream = null;//資源二進制輸入流
9
10 int jarFilesLength = jarFiles.length;//classpath中的jar包個數
11 int repositoriesLength = repositories.length;//倉庫數(classpath每一段稱為repository倉庫)
12 int i;
13 Resource resource = null;//加載的資源實體
14 boolean fileNeedConvert = false;
15 //對每個倉庫迭代,直接找到相應的entry,如果查找的資源是一個獨立的文件,在這個代碼塊可以查找到相應資源,
16 //如果是包含在jar包中的類,這段代碼並不能找出其對應的資源
17 for (i = 0; (entry == null) && (i < repositoriesLength); i++) {
18 try {
19 String fullPath = repositories[i] + path;//倉庫路徑 加資源路徑得到全路徑
20 Object lookupResult = resources.lookup(fullPath);//從資源庫中查找資源
21
22 if (lookupResult instanceof Resource) {
23 resource = (Resource) lookupResult;
24 }
25 //到這里沒有拋出異常,說明資源已經找到,現在構造entry對象
26 if (securityManager != null) {
27 PrivilegedAction dp =
28 new PrivilegedFindResource(files[i], path);
29 entry = (ResourceEntry)AccessController.doPrivileged(dp);
30 } else {
31 entry = findResourceInternal(files[i], path);//這個方式只是構造entry並給其codebase和source賦值
32 }
33 //獲取資源長度和最后修改時間
34 ResourceAttributes attributes =
35 (ResourceAttributes) resources.getAttributes(fullPath);
36 contentLength = (int) attributes.getContentLength();
37 entry.lastModified = attributes.getLastModified();
38 //資源找到,將二進制輸入流賦給binaryStream
39 if (resource != null) {
40 try {
41 binaryStream = resource.streamContent();
42 } catch (IOException e) {
43 return null;
44 }
45 //將資源的最后修改時間加到列表中去,代碼略去,參加源碼
46
47 }
48
49 } catch (NamingException e) {
50 }
51 }
52 if ((entry == null) && (notFoundResources.containsKey(name)))
53 return null;
54 //開始從jar包中查找
55 JarEntry jarEntry = null;
56 synchronized (jarFiles) {
57 if (!openJARs()) {
58 return null;
59 }
60 for (i = 0; (entry == null) && (i < jarFilesLength); i++) {
61 jarEntry = jarFiles[i].getJarEntry(path);//根據路徑從jar包中查找資源
62 //如果jar包中找到資源,則定義entry並將二進制流等數據賦給entry,同時將jar包解壓到workdir.
63 if (jarEntry != null) {
64 entry = new ResourceEntry();
65 try {
66 entry.codeBase = getURL(jarRealFiles[i], false);
67 String jarFakeUrl = getURI(jarRealFiles[i]).toString();
68 jarFakeUrl = "jar:" + jarFakeUrl + "!/" + path;
69 entry.source = new URL(jarFakeUrl);
70 entry.lastModified = jarRealFiles[i].lastModified();
71 } catch (MalformedURLException e) {
72 return null;
73 }
74 contentLength = (int) jarEntry.getSize();
75 try {
76 entry.manifest = jarFiles[i].getManifest();
77 binaryStream = jarFiles[i].getInputStream(jarEntry);
78 } catch (IOException e) {
79 return null;
80 }
81 if (antiJARLocking && !(path.endsWith(".class"))) {
82 //解壓jar包代碼,參見源碼
83 }
84 }
85 }
86 if (entry == null) {
87 synchronized (notFoundResources) {
88 notFoundResources.put(name, name);
89 }
90 return null;
91 }
92 //從二進制流將資源內容讀出
93 if (binaryStream != null) {
94 byte[] binaryContent = new byte[contentLength];
95 //讀二進制流的代碼,這里省去,參見源碼
96 entry.binaryContent = binaryContent;
97 }
98 }
99 // 將資源加到緩存中
100 synchronized (resourceEntries) {
101 ResourceEntry entry2 = (ResourceEntry) resourceEntries.get(name);
102 if (entry2 == null) {
103 resourceEntries.put(name, entry);
104 } else {
105 entry = entry2;
106 }
107 }
108 return entry;
109 }




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM