设为首页 加入收藏

TOP

SPI机制是如何规避双亲委派机制的?(一)
2023-09-09 10:26:00 】 浏览:75
Tags:SPI 何规避 双亲委

SPI是如何规避双亲委派机制的?

1、何为双亲委派机制?

双亲委派机制是什么?

双亲委派机制指的是Java中类加载机制的特性。

双亲委派机制是作用于什么地方?

双亲委派机制主要作用于类加载的时候。

类加载器

首先需要清晰的知道,双亲委派机制指的是类加载的特性。在了解其特性之前,我们需要先了解类加载器有哪些(不考虑自定义加载器的情况)。

加载器 解释
BootStrap加载器 最为顶层的加载器,负责加载System.getProperty("sun.boot.class.path")下的Jar包,主要是jre\lib目录下的内容。该类加载器为C实现,在Java中无法获取
Ext类加载器 扩展类加载器,负责加载System.getProperty("java.ext.dirs")下的Jar包,主要是jre\lib\ext下的内容。在Java中对应ExtClassLoader(注意此处以jdk8为例,jdk11中有所改变)。
App类加载器 应用类加载器,负责加载System.getProperty("java.class.path")下的Jar包,主要是自身程序加载的包。在Java中对应AppClassLoader(注意此处以jdk8为例,jdk11中有所改变)。

类加载器之间的结构如何:

可以看出来,App类加载器是最小的一层,也是我们开发用户接触最多的一层,越往上加载的类就越核心。

双亲委派机制是什么样的结构?

双亲委派机制其实就是描述类加载器加载类的顺序及其特点。

我们开发者需要去加载类的场景每天都在接触,例如在代码中new Car(我们自己的类),此时就是需要去加载这个类。在触发加载类的时候,开发者处于加载器的最低层。那么就可以看作成:App类加载器去加载Car这个类

而实际上的加载顺序是这样的:

App类加载器--通知-->Ext类加载器--通知-->BootStrap类加载器

BootStrap类加载器--发现找不到该类,则向下返回-->Ext类加载器--发现找不到该类,继续向下返回-->App类加载器(当前类加载器如果找不到该类则抛出异常,否则加载成功)

上述为双亲委派机制加载类时的顺序,其特点为先向上通知到最顶层,再由最顶层往下尝试,直到成功加载或到达发送加载类请求的加载器。

这种加载特点最大的作用如下:

安全性:由于Java核心类均有BootStrap加载器、Ext加载器去加载,再加上这种加载类的特性,可以有效防止Java核心类被篡改,正常的Java应用无法修改核心类实现。不仅可以应用在Java核心类中,当我们的应用是插件式时,此方式也可以防止插件中篡改主程序的代码。

2、SPI是什么?

上面我们讲述了双亲委派机制,现在要讲述SPI。

SPI是什么?

SPI(Service Provider Interface)是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用。

例如数据库驱动中java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将功能实现剔除到程序之外,这针对与模块化解耦有很大的作用。

例如下图:

除数据库驱动以外,例如日志框架、Dubbo等也涉及到SPI机制。

在上图中,例如当我们需要具体Driver实现的时候,直接通过JDK的API:

ServiceLoader<java.sql.Driver> serviceLoader = ServiceLoader.load(java.sql.Driver.class);
for (java.sql.Driver driver : serviceLoader) {
     // mysql、pg、oracle、db2等
}

注意,SPI机制存在一些约定,这些约束如下:

  1. 三方接口需在META-INF/services/${interface_name}文件中列举实现类,每一个实现类为一行。例如数据库这,那么示例如下:

    META-INF/services/java.sql.Driver

com.mysql.cj.jdbc.Driver
org.postgresql.Driver
oracle.jdbc.OracleDriver
com.ibm.db2.jcc.DB2Driver

? 2.定义的实现类必须实现对应接口

? 3.实现类必须提供无参构造器

3、为什么说SPI规避了双亲委派机制?

? 注意,我们前面说了双亲委派机制中,加载器会往上层加载器递交加载请求,我们已知java.util.ServiceLoader的类加载器为BootStrap加载器。此加载器已经是最顶层,无更加上层的加载器。而按照加载器职责的约定,ServiceLoader所属类加载器的职责是加载jdk核心类,其是无法加载到用户的类。例如下图:

? 现在的问题是:既然ServiceLoader的类加载器是最顶层的,其加载职责不负责我们自己的类,那么它是如何加载到类似JDBC这种实现类的呢?

附:ServiceLoader的类加载器是BootStrap类加载器,在程序中是无法获取到该类的类加载器的。

4、SPI是如何规避双亲委派机制的?

? 要搞清楚这个问题的原因,得先确认我们使用SPI的入口:

ServiceLoader<Xxxx> serviceLoader = ServiceLoader.load(Xxxx.class);

? 进入该方法,寻找其实现的方式:

java.util.ServiceLoader#load(java.lang.Class)

? 注意此处获取了当前线程的类加载器,而在线程中调用该类方法的是我们用户自己。那么这里就理解为获取到了用户的类加载器。

? 再往该方法中查找,找到该段代码:

java.util.ServiceLoader#ServiceLoader

? 注意该段代码中,cl为上一步获取到的类加载器,如果发现类加载器不存在,会再次获取系统默认加载器,这个系统默认加载器在常规情况下是用于加载启动类的加载器(jdk注释中解释),而启动类则是我们用户自己定义的类,这里毋庸置疑也会是应用类加载器。

? 从上面的代码中我们总结出来,ServiceLoader获取了我们的应用类加载器,至此load方法入口基本上没有其他内容可以细看。

? 为减轻文章阅读压力,直接跳转到该方法

java.util.ServiceLoader.LazyIterator#nextService

image

? 注意这里的loader是我们前面获取到的应用类加载器,这个方法中是获取到了具体需要实例化的实现类,即将对其进行实例化, 在这之前需要先获取到Class,这里使用Class.forName(class, false, ClassLoader)方法,这个方法的含义是使用指定的类加载器去加载指定的类。既然这里的类加载器是应用类加载器,那么类加载顺序

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Redis的五大数据类型的数据结构 下一篇java与es8实战之五:SpringBoot应..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目