跳过正文
  1. 博客/
  2. 后端/
  3. 框架/

SPI破坏了双亲委派吗

4 分钟· ·
后端 框架 Java
作者
Allen
一个强大、轻量级的 Hugo 主题。
目录

最近在学JVM的时候,把ClassLoader部分给过了一遍,谈到ClassLoader少不了双亲委派,谈到双亲委派少不了说破坏双亲委派的SPI,也看到了知乎上一些观点 ,这个时候我就疑惑了,有两个问题围绕在我头上,什么是双亲委派,为什么说SPI破坏了双亲委派,这篇博客就从源码出发,讲一讲我的理解

一、什么是双亲委派
#

双亲委派我归纳为两点:

  1. 不可以越权
  2. 委托链机制

现在我来讲讲为什么这样总结

不可以越权
#

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

资料

https://enfangzhong.github.io/2019/12/17/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3Java%E8%99%9A%E6%8B%9F%E6%9C%BA%E4%B9%8B%E7%A0%B4%E5%9D%8F%E5%8F%8C%E4%BA%B2%E5%A7%94%E6%B4%BE%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6/

相关文章

AOP再思考
4 分钟
后端 框架 Java
压测心得
2 分钟
后端 框架 Java
Stream源码(1):如何实现去重
3 分钟
后端 框架 Java Stream
Dubbo浅探
3 分钟
后端 框架 Java Dubbo
Spring Cloud Alibaba浅探
2 分钟
后端 框架 Java SpringBoot
SpringCloud浅析
5 分钟
后端 框架 Java SpringBoot