前面一直都是在介绍概念,正所谓实践才是检验真理的唯一标准,这里我们就来动手实现一个简单的类加载器,这里实现的类加载器很简单,但足够理解前面的所说的内容。
findLoadedClass方法简介 ClassLoader的findLoadedClass方法,传入一个全类名,如果当前ClassLoader的引用和全类名之间的关系被JVM所记录,那么将返回这个Class的实例。
前文介绍过, SystemDictionary,系统字典是保存Java加载类型的数据结构。它通过Class的全类名以及ClassLoader实例来定位一个Class实例引用。
在ClassLoader的实现中,我们没有看到如何将对应关系添加到SystemDictionary中,原因在于添加到SystemDictionary的逻辑在JVM的实现中,并且在某些情况下才会触发,而findLoadedClass方法仅仅简单的去SystemDictionary中查询一下而已。
因此从findLoadedClass中查询出什么已经变得不在重要,而在哪些情况下被添加到SystemDictionary才是关键。在SystemDictionary中生成ClassLoader和全类名的记录有如下情况:
确由该ClassLoader加载了类,记入SystemDictionary;
通过Class.forName(String name)
或者Class.forName(String name, boolean initialize, ClassLoader loader)
来加载全类名为name的类,如果传递的ClassLoader与真实加载类的ClassLoader不同,则记入SystemDictionary;
代码中出现类型,结果同2。
测试代码: 基础的测试类如下,后面会用到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 JavaLoaded.java、ClassForName.java、LoadClass.java public class JavaLoaded { public void jm () { System.out.println(this .getClass().getClassLoader() + " loaded JavaLoaded" ); } } public class ClassForName { public void cm () { System.out.println(this .getClass().getClassLoader() + " loaded ClassForName" ); } } public class LoadClass { public void lm () { System.out.println(this .getClass().getClassLoader() + " loaded LoadClass" ); } }
简单的类加载器,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 public class CachedClassLoader extends URLClassLoader { private final Map<String, Class<?>> CACHE = new ConcurrentHashMap <String, Class<?>>(); private String name; public CachedClassLoader (URL[] urls) { super (urls, null ); } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { Class<?> clazz = CACHE.get(name); if (clazz != null ) { return clazz; } return super .loadClass(name); } public final void injectClass (String name, Class<?> clazz) { if (name != null && clazz != null ) { CACHE.put(name, clazz); } } public Class<?> findLoadedClass1(final String name) { return this .findLoadedClass(name); } public void setName (String name) { this .name = name; } @Override public String toString () { return name; } }
这个类只是简单的重写了下loadClass方法,先去缓存中查找是否已有类加载器加载过这个类,如果加载了,则不重新加载类。注意这里只是简单的demo,所以不要去纠结这个类加载器加载类会出现的一些问题。
测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 public class Main { public static void main (String[] args) throws Exception { Main main = new Main (); main.loadedClass(); } public void loadedClass () throws Exception { String s = "studydemo-1.0-SNAPSHOT.jar" ; File file = new File (s); CachedClassLoader j1 = new CachedClassLoader (new URL []{file.toURI().toURL()}); j1.setName("1#" ); CachedClassLoader j2 = new CachedClassLoader (new URL []{file.toURI().toURL()}); j2.setName("2#" ); j1.injectClass("club.zhangke.classLoaderStudy.JavaLoaded" , j2.loadClass("club.zhangke.classLoaderStudy.JavaLoaded" )); j1.injectClass("club.zhangke.classLoaderStudy.ClassForName" , j2.loadClass("club.zhangke.classLoaderStudy.ClassForName" )); j1.injectClass("club.zhangke.classLoaderStudy.LoadClass" , j2.loadClass("club.zhangke.classLoaderStudy.LoadClass" )); Class<?> a1 = j1.loadClass("club.zhangke.classLoaderStudy.Main" ); Method aM = a1.getMethod("m" , new Class <?>[]{}); aM.invoke(a1.newInstance(), new Object []{}); System.out.println("1# findLoaded." ); System.out.println(j1.findLoadedClass1("club.zhangke.classLoaderStudy.JavaLoaded" )); System.out.println(j1.findLoadedClass1("club.zhangke.classLoaderStudy.ClassForName" )); System.out.println(j1.findLoadedClass1("club.zhangke.classLoaderStudy.LoadClass" )); System.out.println("2# findLoaded." ); System.out.println(j2.findLoadedClass1("club.zhangke.classLoaderStudy.JavaLoaded" )); System.out.println(j2.findLoadedClass1("club.zhangke.classLoaderStudy.ClassForName" )); System.out.println(j2.findLoadedClass1("club.zhangke.classLoaderStudy.LoadClass" )); } public void m () throws Exception { System.out.println(this .getClass().getClassLoader() + " loaded Main" ); JavaLoaded javaLoaded = new JavaLoaded (); javaLoaded.jm(); Class<?> classForName = Class.forName("club.zhangke.classLoaderStudy.ClassForName" ); classForName.getMethod("cm" , new Class []{}).invoke(classForName.newInstance(), new Object []{}); Class<?> loadClass = Main.class.getClassLoader().loadClass("club.zhangke.classLoaderStudy.LoadClass" ); loadClass.getMethod("lm" , new Class []{}).invoke(loadClass.newInstance(), new Object []{}); } }
该测试用例构建了两个CachedClassLoader,也就是1#和2#,用2#加载了JavaLoaded、ClassForName和LoadClass,并将其注入给1#,这样1#在进行loadClass时,将会优先命中缓存中2#加载的以上3个类。
调用Main之后,在依次查询1#和2#的findLoadedClass方法。 输出结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 1# loaded Main 2# loaded JavaLoaded 2# loaded ClassForName 2# loaded LoadClass 1# findLoaded. class com.murdock.book.jarviewer.trap.JavaLoaded class com.murdock.book.jarviewer.trap.ClassForName null 2# findLoaded. class com.murdock.book.jarviewer.trap.JavaLoaded class com.murdock.book.jarviewer.trap.ClassForNameruxia class com.murdock.book.jarviewer.trap.LoadClass
首先从上面的m函数的运行结果可以看出,m函数使用的class都是由2#加载的,至于为什么JarLoad这个类为什么还是2#加载的,这个需要看CacheClassLoader里面的具体代码就可以明白,这里就不具体解释。 可以看到JavaLoaded、ClassForName和LoadClass都是2#所加载,因此调用2#的findLoadedClass均能够找到这些Class,这对应于情况1。
下面为什么JavaLoaded和ClassForName为什么还是能够被发现,具体原因如下。 1#没有加载3个类,但是由于JavaLoaded类型出现在Main中,而Main是由1#加载,因此1#找寻不到JavaLoaded,则由1#.loadClass进行加载,结果委派给了2#成功加载。但是由于1#不等于2#,所以在SystemDictionary中会增加一条记录,这使得调用1#.findLoadedClass也能找到JavaLoaded,这对应与情况3。
ClassForName类似于JavaLoaded,对应于情况2。
而直接调用1#.loadClass(“LoadClass”),只是相当于一个简单java方法调用,返回了LoadClass的类型实例,但是没有在System新增记录,因此调用1#.findLoadedClass(“LoadClass”)是无法找到LoadClass的。