我目前的系统可能需要自己实现类加载器,想要参考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 }