关于SPI与Java双亲委派机制的第二次被破坏

简介

最近有个同学问我双亲委派机制的第二次被破坏是怎么样的,我大致的说了一下,然后他说他没怎么明白,那我就写篇文章,说一下SPI,线程上下文类加载器和双亲委派机制的第二次被破坏。

SPI机制

SPI机制全名服务提供者接口,是面向接口编程的一种范例。简而言之就Java语言(上游)提供一个服务接口(Service Interface),然后下游服务厂商实现这个服务接口提供服务(Service Provider)。SPI机制分三部分,这两个部分应该好理解。

  • Service Interface(上游提供的服务接口)
  • Service Provider(下游提供的服务接口的实现)

再说第三个部分Service Loader,其实这是Java语言为了方便形成的,这个我们慢慢引出来,我们先思考只有上面两个部分的时候,我们会怎么干,以数据库驱动为例。

参考以下对话
Java语言:现在Java提供了一个数据库驱动接口(java.sql.Driver),你们这些数据库服务提供厂商假如想让Java可以使用你们的数据库服务,就得自己实现这个接口,好让我调用你们的实现类。

Oracle:好的,老大,我实现了Java的java.sql.Driver接口,这个实现类就是oracle.jdbc.driver.OrcaleDriver,我现在把这个类和与其相关的一些类一起打成一个jar包,您到时候下载,然后把jar包放到classpath路径下,最后用这样的代码就可以得到我们的实现类了,然后使用了。

Class oracleDriver = Class.forName("oracle.jdbc.driver.OrcaleDriver");
Driver dirver = (Driver)oracleDriver.newInstance();

Java语言:嗯,好,那我就去用了。

随着越来越多的数据库厂商实现这个接口,Java语言开始不耐烦了,凭什么我要记住各个厂商实现类的全限定名(就是上面代码反射那块),我是老大,你们是服务提供商,我要实现一种我不用记住各个厂商实现类的全限定名,但是可以无差别使用他们他们实现类的方法。然后Java语言想,我不用记住这个实现类全限定名,那么就让服务厂商们把这个实现类的全限定名自己写在那个jar包里面的一个特定文件里,这个文件的文件名就写成提供服务的这个接口的全限定名吧(这个例子中体现为java.sql.Driver),文件的地址就放在jar包下的META-INF/services/

Java语言:你们这些服务厂商之后记得把在你们提供服务的jar包下的META-INF/services/文件夹下面建立一个叫做java.sql.Driver文件,文件里面就填你们实现类的全限定名。

服务厂商:收到,老大。

现在Java语言到特定的文件里既可以得到实现类的全限定名了,但是Java还是不满意,因为虽然自己不用记这个文件名,但是自己每次都得手写一段从这个特定的文件得到实现类全限定名的代码,再重新利用反射,形成实例。能不能把这段代码给它封装成一个方法呢?这样SPI机制的第三个部分服务加载者(Service Loader)就出来了,这个服务加载者的作用就是到META-INF/services/提供服务接口的全限定名 这个文件下去找到实现类的全限定名,并实例化这个类,然后把实例放到ServiceLoader存着。要用的的时候拿就行了,其实SPI机制到此就已经结束了。

双亲委派机制被破坏

但是Java语言太懒了,它连调用ServiceLoader的方法都不想主动调用,想让这个调用自动化,然后就利用类加载的时候会自动初始化的特点,实现了调用自动化,这个期间出现了一点问题,不过好在解决了。下面说一下这个问题,以及其解决方法——线程上下文类加载器。

Java想实现自动化(放到某个类(例如DriverManager)的静态代码块中,用的时候直接利用类加载机制,自动调用),所以这些自动化的代码必然是jdk写了然后封装到基础类库里面的,而这些基础类库类(例如DriverManager)是由启动类加载器加载的,那么启动类加载器在加载(DriverManager)的时候,他遇到了去加载厂商实现类的代码,启动类加载器是没办法加载的(启动类加载器只能加载一些jdk自带的基础的包下的类),而且启动类是没有父加载器的(其实他们之间并非继承关系,而是组合关系),那么这些服务实现类无法加载,所以出现了线程上下文类加载器,就是启动类加载器加载到一半,发现这些类自己加载不了。那么就让线程上下文类加载器去加载,这个线程上下文类加载器就是当在执行这段代码的线程是有个上下文类加载器(其实就是应用程序类加载器),就让这个类加载器去加载。因为这个服务实现类的加载和实例化的过程之前说了是放到Service Loader里面的,所以这个让线性上下文类加载器去加载代码的过程就在Service Loader里面。

总结

回顾整个加载DriverManager的过程,先是由启动类加载器加载,然后由线程上下文类加载器加载。这就违背了类的双亲委派机制。

再回首看看SPI机制,ServiceLoader的出现就是Java语言图方便。把找服务厂商实现类,然后实例化的过程封装起来了,放到了ServiceLoader里面。至于那个后面Java想调用自动化的过程,我认为不属于SPI机制的一部分。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容