最近在学JVM的时候,把ClassLoader部分给过了一遍,谈到ClassLoader少不了双亲委派,谈到双亲委派少不了说破坏双亲委派的SPI,也看到了知乎上一些观点 ,这个时候我就疑惑了,有两个问题围绕在我头上,什么是双亲委派,为什么说SPI破坏了双亲委派,这篇博客就从源码出发,讲一讲我的理解
一、什么是双亲委派 #
双亲委派我归纳为两点:
- 不可以越权
- 委托链机制
现在我来讲讲为什么这样总结
不可以越权 #
ClassLoader分为四种:启动类加载器 > 扩展类加载器 > 应用程序类加载器 > 自定义
我们看看Class.forName
的源代码
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
当我们需要初始化一个类的时候,我们得会通过Reflection.getCallerClass
获取到调用方的ClassLoader,通过这个ClassLoader去加载类
PS: 其中forName0
是一个native方法,最终会调用C++代码find_class_from_class_loader
从classLoader中 调用loadClass
方法
也就是说其实源码告诉我们,我们加载一个类,默认是从加载这个类的ClassLoader去加载别的类,说起来很拗口,其实就是说你这个代码是被哪个加载器加载的,默认只能加载被这个
ClassLoader加载过的
当然在JDK1.2 支持传入ClassLoader,这个后面会讲 ,接下来我们讲什么是委托链机制
委托链机制 #
我们来看ClassLoader.loadClass
方法
Class<?> c = findLoadedClass(name); // 从缓存中加载
if (c == null) {
if (parent != null) { // 父类不为空 委托父类加载
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name); // 当父类为空的时候就是根加载器,委托根加载器加载
}
if (c == null) {
// 最后从自己这里取
c = findClass(name);
}
代码很简单,就是首先从缓存中取,没找到然后在从父类里找,找不到就到自己这找,其实一个很有意思的点就是,从缓存中取的是调用的native方法,最后调用的C代码就是下面这个
Klass* k = SystemDictionary::find_instance_or_array_klass(klass_name, //类名
h_loader, // classLoader地址
Handle(),
CHECK_NULL);
原理是从hash表中,通过类名和加载器类名进行查找,也就是说,对于一个类来说,java判断是否相同是根据它的类名和加载该类的ClassLoader的实例来实现的
这两点其实很容易理解,接下来我们看看什么是SPI
二、SPI是什么 #
SPI全名是Service Provider Interface,这个其实就是一个工具类
原理非常的简单,我归纳成两点
当调用静态方法ServiceLoader.load
时候根据传入的类或者接口名,读取 META-INF/services/{name} 下面的文件
读取字符串,然后加载 Class.forName(cn, false, loader)
(cn代表这个字符串,loader代表加载器)
其中最核心的就是调用了 Class.forName 这个方法,你可以看到,这里面调用了我们上面提到的一个重载方法,这个重载方法最大的差别就是可以自己传入自己想传入的ClassLoader
接下来看看为什么我们说它破坏了双亲委托
为什么说SPI破坏了双亲委派 #
我们回顾一下前面总结,SPI它破坏了哪点呢,其实就是我们之前说的第一点 ** 不可以越权** ,这个SPI竟然可以自己传入classLoader,而且默认它取的是当前线程的ClassLoader
我们来看看经常提到的JDBC中,为啥说SPI破坏了java.sql.DriverManager
的双亲委托呢,因为这个java.*
包是根加载器加载的,然而我们却在java.sql.DriverManager 中使用
Class.forName 调用了三方jar包(使用应用程序类加载器加载)
总结 #
所以其实从本质上来说,为啥我们说SPI破坏了双亲委派破坏了,坏就坏在它的代码在java.sql.DriverManager
中,假如我们在我们代码中使用这个Class.forName,我们没有破坏这个双亲委派
其实即使在java.sql.DriverManager
我们不使用SPI,我们也可以破坏双亲委派,因为它会读取jdbc.drivers
这个属性,我们会读取这个字符串,然后使用Class.forName(aDriver,true,ClassLoader.getSystemClassLoader());
来加载里定义的三方Driver
资料