原文例子
package org.fenixsoft.classloading;
import java.io.IOException;
import java.io.InputStream;
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException { //重写了loadClass方法
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
//getClass().getResourceAsStream会尝试从匿名内部类class文件自身所在目录下查找其他类文件
//因为是相对路径
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
//如果代码走至此处,符合双亲委派模型机制
return super.loadClass(name); //但是代码永远不会执行到此处
}
byte[] b = new byte[is.available()];
is.read(b);
//实际执行的是此处代码,即每次都是自定义类加载器自身加载类的
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
Object obj = myLoader.loadClass("org.fenixsoft.classloading.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof org.fenixsoft.classloading.ClassLoaderTest);
//这是我加的第三个打印
System.out.println(obj.getClass().getClassLoader());
//这是我加的第四个打印
System.out.println(org.fenixsoft.classloading.ClassLoaderTest.class.getClassLoader());
}
}
执行结果:
class org.fenixsoft.classloading.ClassLoaderTest
false
org.fenixsoft.classloading.ClassLoaderTest$1@7852e922
sun.misc.Launcher$AppClassLoader@73d16e93
代码清单7-8中构造了一个简单的类加载器,尽管它极为简陋,main
方法内创建一个匿名内部类对象myLoader
,重写了loadClass()
方法,一般情况下,实现自定义ClassLoader
,是不会重写loadClass()
方法,因为默认的loadClass()
方法是双亲委派模型的基础,因此,myLoader
破坏了双亲委派模型(虽然有代码super.loadClass,但永远执行不到),效果就是myLoader
是个独立存在的类加载器,和已存在的类加载器(AppClassLoader、ExtClassLoader、启动类加载器)不会存在瓜葛,不会存在误区。
后面会单独写一篇文章,讲述这个误区。
原文解释:
myLoader
可以加载与自身在同一路径下(原因是相对路径)的Class文件,我们使用这个类加载器去加载了一个名为“org.fenixsoft.classloading.ClassLoaderTest
”的类,并实例化了这个类的对象。
两行输出结果中,从第一行可以看到这个对象确实是类org.fenixsoft.classloading.ClassLoaderTest
实例化出来的,但在第二行的输出中却发现这个对象与类org.fenixsoft.classloading.ClassLoaderTest
做所属类型检查的时候返回了false
。这是因为Java虚拟机中同时存在了两个ClassLoaderTest
类,一个是由虚拟机的应用程序类加载器所加载的,另外一个是由我们自定义的类加载器加载的,虽然它们都来自同一个Class文件,但在Java虚拟机中仍然是两个互相独立的类,做对象所属类型检查时的结果自然为false。
笔者注释
原文注释是不是看不懂?下面我来进行详细解释。
我们先从后面2条打印开始分析,第三条打印:
org.fenixsoft.classloading.`ClassLoaderTest$1`@7852e922
表明obj对象的类加载器是我们自定义的匿名内部类ClassLoaderTest$1
,$1
是自动生成的内部类编号:
如果我们定义一个带名称的子类替匿名内部类,例如
public class `SelfClassLoader` extends ClassLoader {
那么此时就会输出
org.fenixsoft.classloading.`SelfClassLoader`@7852e922
再来看第四条打印:
sun.misc.Launcher$AppClassLoader@73d16e93
这个比较简单,表明是系统类加载器(也可以称应用程序加载器),这个是JDK自带的。
org.fenixsoft.classloading.ClassLoaderTest.class.getClassLoader()可以理解成main函数所在的类通过import引入了一个其他类A,此时main的加载器和类A一致,这里均为AppClassLoader
类加载器不是双亲委派模型吗?为何这里类加载器是2个不同对象呢?
我们在前文讲过,ClassLoaderTest中的匿名内部类,虽然继承了ClassLoader,但是复写了loadClass方法,而loadClass方法是双亲委派模型的基础,简单来说这个基类中的loadClass方法逻辑是,“当子类加载器尝试加载一个类时,优先让父类帮助自己去加载,如果父类还有父类,那么会让父类的父类去加载,直至最顶层的启动类加载器也失败了,才会让子类自己去加载”
详细可以参考其的文章
类加载器3 ClassLoader中的loadClass(),findClass(),defineClass() 第一章节 《loadClass》源码里面的注释
类加载器2 双亲委派和破坏双亲委派《2.2、双亲委派的实现》章节的源码里面的注释
再看看我们复写的loadClass方法代码呢,直接读取class文件路径信息,去加载文件流,直接加载了,没有委托父类去加载的代码,因此,我们才说,这个自定义的类加载器破坏了双亲委派模型。