自学内容网 自学内容网

双亲委派机制&SPI

SPI如何破坏双亲委派机制?可根据以下概念一步步深入

什么是双亲委派机制?

双亲委派机制是Java类加载器体系中采用的一种类加载策略,旨在保证类加载的安全性和稳定性。

这一机制规定了类加载的顺序和规则,即当一个类加载器收到类加载请求时,首先不会自己去尝试加载这个类,而是将这个任务委托给它的父类加载器去完成,只有当父加载器无法加载该类时(表现为抛出ClassNotFoundException),才会尝试自己加载。

 

优点:核心的包,谁也改变不了,保证了安全性。

缺点:在于它的隔离性,Bootstrap加载器无法去加载应用程序的类。(后面就引出SPI的概念)

1 先去自定义加载器加载,自定义加载器没有加载过这个类,委派给应用类加载器

2 应用类加载器也没有加载过这个类,委托给扩展类加载器

3 扩展类加载器没有加载过这个类,委托给根加载器,根加载器去rt.jar的包里去找需要加载的类,rt.jar包里没有,就让扩展类加载器去ext路径下去找

4 扩展类加载器也没有找到,就让应用类加载器去CLASSPATH下面找…

举例验证:当你自定义一个java.lang.String的类,去new这个对象时,加载的仍然是Java自带的String对象,而不是你自定义的String对象。(详细如下图)

什么是SPI?

SPI全称ServiceProviderInterface,是Java提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。SPI的作用就是为这些被扩展的API寻找服务实现。

SPI使用方式?

1 定义一个接口,并创建它的实现类

2 然后在项目的src/main/resource/META-INF/services目录下创建一个与上述接口的全限定名一致的文件,在这个文件中写入接口的实现类的全限定名

3 运行时,会查找加载这些实现类。(比如下面举例的加载mysql驱动程序)

为什么你在自己的JDBC项目中没有找到META-INF/services/java.sql.Driver文件?

如果你的项目是直接使用像Spring Boot这样的现代框架,它们通常有自动配置或者依赖管理特性,能自动处理JDBC驱动的加载,而不需要你手动放置这个文件。特别是当你通过Maven或Gradle等构建工具管理依赖时,驱动的SPI配置已经是驱动包内部的事情,你的项目不需要直接包含这个文件。

什么是rt.jar?

rt.jar 是 Java Runtime Environment (JRE) 的一个核心组件,全称为 Runtime JAR

如何“破坏”?

SPI核心类在rt.jar 位于Bootstrap ClassLoader启动类加载器中,但是SPI需要实现来自不同厂商提供的数据库Driver实现类,而这些来自不同厂商的SPI接口实现类(如com.mysql.cj.jdbc.Driver)位于Application ClassLoader应用类加载器中

为何说“破坏”?

尽管SPI本身是由启动类加载器加载,但是它间接的通过应用类加载器加载第三方驱动类,绕过了严格的双亲委派机制

为什么要破坏?

因为使用双亲委派有一定的局限性,在正常情况下,用户代码一定是依赖核心类库的,所以双亲委派加载机制是没有问题的,但是在加载核心类库时,又要反过来使用用户代码,双亲委派就无法满足。这是什么样的一个场景呢?比如jdbc利用Driver.Manager.getConnection获取连接时,DriverManager是由根类加载器Bootstrap ClassLoader加载的,在加载DriverManager时,会执行其静态方法,加载驱动程序(也就是Driver接口的实现类),这些实现类都是由第三方数据库厂商提供,根据双亲委派原则,第三方类不可能被根类加载器加载

双亲委派机制源码解析?

这里forName进行加载类,forName方法的getClassLoader是获取caller对应的ClassLoader,caller就是指当前调用的forName方法的那个类,即ReflectionTest这个类,因为这个类是自己写的,所以在CLASSPATH路径下,获取到的加载器应该是应用类加载器(App ClassLoader),解析去会尝试用应用类加载器加载Persion类

通过findLoadedClass(name),去判断当前类加载器有没有加载过,没有加载过,并且parent加载器不为空,就调用parent加载器的loadClass方法…(由此递归)

当到根加载器调用loadClass方法的时候,由于根加载器没有parent,所以就执行findBootstrapClassOrNull(name)去尝试加载类,如果加载不到再让子类去加载

SPI破坏双亲委派机制- JDBC应用场景源码解析?

补充说明:Class.forName("com.ibm.db2.jcc.DB2Driver");这种写法源码里是获取的应用类加载器,这里没有问题,正常的做法。

          但是java1.6开始,后期的SPI版本是不需要写这句代码的,即不需要显示的指定驱动程序,DriverManager会自动查找合适的驱动,从而引出SPI为什么会破坏双亲委派机制。

1 假设这里按照双亲委派的加载思想,这里ServiceLoader是属于rt包,属于根加载器,根加载器去加载mysql的驱动,肯定是找不到的,而且你也没有向下去委托扩展类加载器的路,因为你起步就是根加载器,没法往下递归了,根加载器加载不到,就结束了

2 但是,SPI这里并没有这样去做,而是从Thread.currentThread().getContextClassLoader()获取类加载器,这里获取到的是应用类加载器(为什么获取到应用类加载器?下面进行了详细解释),然后由应用类加载器完成加载

 

ClassLoader构造函数

获取系统的classLoader

初始化classLoader,从Launcher中获取ClassLoader

Launcher是干嘛的?Launcher是一个启动类,Lanucher创建了扩展类加载器,创建了应用类加载器,并把扩展类加载器添加成父类,并且把应用类加载器添加到当前线程的ContextClassLoader 即Thread.currentThread().SetContextClassLoader()

但是,上图中并没有创建bootstrap加载器,为什么呢?因为bootstarp加载器是jvm内部创建,默认是所有机载器的父类。

总结:

1 SPI加载方式实际上是通过获取当前线程的应用类加载器加载的rt包的接口实现类,这些实现类是存在于CLASSPATH中

2 而双亲委派机制,是根据你当前调用forName的那类,来决定起点加载器是哪一个,再向上委派,向下加载。


原文地址:https://blog.csdn.net/weixin_44038236/article/details/142458590

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!