jdk的spi机制(双亲委派)----spi机制(一)

spi(Service Provider Interface)

spi是一种api的方式,为了能够对第三方组件更好扩展的一种机制,可以增强框架的扩展或者替换一些组件。

简单的总结下java SPI机制的思想:我们系统里抽象的各个模块,往往有很多不同的实现方案,比如日志模块、xml解析模块、jdbc模块等方案。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。 Java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。

Java SPI的具体约定为:当服务的提供者提供了服务接口的一种实现之后,在jar包的META-INF/services/目录里同时创建一个以服务接口命名的文件,该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。基于这样一个约定就能很好的找到服务接口的实现类,而不需要再代码里制定。jdk提供服务实现查找的一个工具类:java.util.ServiceLoader。

类加载机制

类加载机制

当一个类加载器收到了类加载的请求的时候,他不会直接去加载指定的类,而是把这个请求委托给自己的父加载器去加载。只有父加载器无法加载这个类的时候,才会由当前这个加载器来负责类的加载。

Java中提供如下四种类型的加载器,每一种加载器都有指定的加载对象,具体如下

Bootstrap ClassLoader(启动类加载器) :主要负责加载Java核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。

Extention ClassLoader(扩展类加载器):主要负责加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。

Application ClassLoader(应用程序类加载器) :主要负责加载当前应用的classpath下的所有类

User ClassLoader(用户自定义类加载器) : 用户自定义的类加载器,可加载指定路径的class文件

这四种类加载器存在如下关系,当进行类加载的时候,虽然用户自定义类不会由bootstrap classloader或是extension classloader加载(由类加载器的加载范围决定),但是代码实现还是会一直委托到bootstrap classloader, 上层无法加载,再由下层是否可以加载,如果都无法加载,就会触发findclass,抛出classNotFoundException.

类加载方法

双亲委派的优势:

避免重复加载 + 避免核心类篡改

采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心JavaAPI发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。

双亲委派的劣势:

不能向下逆向加载

在双亲委派模型中,子类加载器可以使用父类加载器已经加载的类,而父类加载器无法使用子类加载器已经加载的。这就导致了双亲委派模型并不能解决所有的类加载器问题。

双亲委派的破坏

第一次,历史原因,在jdk1.2开始引入双亲委派机制,那么在此之前如 jdk1.1的时候,存在的自定义的类加载器,它们就不符合双亲委派。而实际上,我们可以继承ClassLoader类,然后重写loadClass方法,也可以打破双亲委派。(打破双亲委派,不一定是坏事)

第二次,SPI机制引入了线程上下文特意打破双亲委派,后面撸源码会提到。

第三次,用户对程序动态性的追求导致的;即:代码热替换、模块热部署。

OSGi实现模块化热部署的关键则是它自定义的类加载器机制的实现。每一个程序模块(Bundle)都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换。在OSGi幻境下,类加载器不再是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构。

第四次,是JDK9,引入了模块化,当平台及应用程序类加载器收到类加载请求,在委派给父加载器加载前,要先判断该类是否能够归属到某一个系统模块中,如果可以找到这样的归属关系,就要优先委派给负责哪个模块的加载器完成加载。

经过破坏后的双亲委派模型更加高效,减少了很多类加载器之间不必要的委派操作。

demo搞起来~~~~~~~~~


日志接口


Log4j


Slf4j

在项目中如何使用

Log log4j=new Log4jImpl();

Log slf4j=new Slf4jImpl();

extension and replaceable (Java Spi) 根据接口找不同的实现类实例

(1) 定义一个文件 META-INF/services

(2)定义接口全路径名称文件 com.yjy.spi.javaspi.Log

(3)使用Java Spi 模板代码


spi模板

实现原理:

(1) 根据接口找文件名称  去 指定目录寻找文件名  META-INF/service/com.yjy.spi.javaspi.Log

(2) 解析该配置文件,拿到每一行数据

(3)对每一行数据的类进行实例化

总结:

(1) 为何META-INF/services是因为源码中 private static final StringPREFIX ="META-INF/services/";

(2)为何要创建com.jack.Log文件,是因为 ServiceLoader.load(Log.class);

(3)每一行数据都会被保存到nextName并存到map中。

JAVA SPI 应用场景

jdbc:主要就是获取一个Connection实例

(1) 接口:java.sql.Connection(JDK) Driver  类比Log 

(2)接口实现类有哪些  mysql  oracle  


打破双亲委派代码


spi机制代码


通过不同的协议找不同的driver

JAVA SPI 的缺点

不能按需加载。虽然 ServiceLoader 做了延迟载入,但是基本只能通过遍历全部获取,也就是接口的实现类得全部载入并实例化一遍。如果你并不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。

获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。

多个并发多线程使用 ServiceLoader 类的实例是不安全的。

加载不到实现类时抛出并不是真正原因的异常,错误很难定位。

正因为这些缺点dubbo SPI和spring SPI应运而生,请关注下篇文章~~~

dubbo SPI和Spring SPI (二)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容