首页 > 其他分享 >SPI机制是如何规避双亲委派机制的?

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

时间:2023-08-29 22:12:13浏览次数:36  
标签:-- SPI 双亲 机制 ServiceLoader 加载

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)方法,这个方法的含义是使用指定的类加载器去加载指定的类。既然这里的类加载器是应用类加载器,那么类加载顺序自然就又回到了应用类加载器-->扩展类加载器-->BootStrap类加载器-->扩展类加载器-->应用类加载器,能加载到我们想要的类也就不奇怪了。

​ 看到这里也就明白了为什么使用SPI仍然能正常加载类了。

​ SPI的加载机制看起来虽然方面,但仍然有缺点:

1. 无法实现动态加载、卸载的效果,只有最简单的加载三方类的实现。
1. 由于实现原因,实现类必须提供无参构造器,局限性和扩展性很低

​ 综合来说,SPI简单但局限性大,项目中能接受这些缺点就可以放心使用,如接受不了则可以模拟SPI机制自行实现一套加载机制,自己实现起来扩展性和局限性肯定是原生SPI不能比的。

​ 本次内容结束,如发现内容错误请留言,会尽快改正。

标签:--,SPI,双亲,机制,ServiceLoader,加载
From: https://www.cnblogs.com/daihang2366/p/17665965.html

相关文章

  • 【SPI】SPI总线协议及驱动框架
    SPI通讯协议SPI控制方式SPI采用主-从(master-slave))模式的控制的方式。一个Master设备可以通过提供Clock以及对slave设备进行片选来控制多个Slave设备,SPI协议还规定Slave设备的Clock由Master设备通过SCK管脚提供给Slave设备,Slave设备本身不能产生和控制Clock,没有Clock则Slave设......
  • 深入理解操作系统中进程与线程的区别及切换机制(下)
    前言上一篇文章中我们了解了进程的执行方式,包括早期单核处理器上的顺序执行以及引入多任务概念实现的伪并行。我们还探讨了进程的状态模型。进程可以处于就绪、运行、阻塞和结束等不同的状态。在本篇文章中,我将探讨研究进程的状态模型、控制结构和切换机制。希望通过这篇文章的......
  • jvm 类加载机制
    类加载机制类加载机制是指我们将类的字节码文件所包含的数据读入内存,同时我们会生成数据的访问入口的一种特殊机制。那么我们可以得知,类加载的最终产品是数据访问入口。虚拟机把Class文件加载到内存,并对数据进行校验,转换解析和初始化,形成可以虚拟机直接使用的Java类型,即java.......
  • iic和spi简记
    IIC通信协议两线式串行总线,多用于主控制器和从器件间的主从通信,在小数据量场合使用,有传输距离短,任意时刻只能有一个主机等特性。  SDA(Serialdata)数据线,D代表Data也就是数据,SendData也就是用来传输数据的SCL(Serialclockline)时钟线,C代表Clock也就是时钟也就是......
  • 在 PHP 中,原生并没有提供内置的定时器机制,定时触发的守护进程,其中一个常见的方式是使
    <?phpclassTimerDaemon{private$logfile;private$fp;private$triggerInterval;//触发间隔,以秒为单位private$lastTriggerTime;publicfunction__construct($logfile,$triggerInterval){$this->logfile=$logfile;......
  • 深入理解操作系统中进程与线程的区别及切换机制(上)
    进程所谓进程,大家可以理解为我们打开的应用程序,如微信、QQ、游戏等,但也有系统应用是我们看不见的,可以打开任务管理器一探究竟,我们写的代码程序在服务器上在不运行的情况下,它就是一个二进制文件,并不是进程!一个进程可以包含一个或者多个线程,但对于CPU来说他就是一个任务而已;在......
  • 基于SPI协议的flash驱动控制
    第46章、基于SPI协议的flash驱动控制学习掌握SPI通讯协议的基本知识和概念,理解掌握基于SPI总线的Flash驱动控制的相关内容,熟悉FPGA与SPI器件之间数据通信流程。根据所学知识设计一个基于SPI总线的Flash驱动控制器,实现FPGA对Flash存储器的数据写入、数据读取以及扇区擦除和......
  • Redis持久化机制
    Redis的持久化指的是将内存中的数据持久化到磁盘上,以便在Redis服务器重启或宕机时能够恢复数据。Redis支持两种持久化方式:RDB和AOF。RDB持久化RDB全称RedisDatabaseBackupfile(Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘......
  • django 解决queryset惰性机制,实现实时查询
    django在第一次查询后,就把数据进行缓存。如果对数据进行操作后,再进行查询时直接去缓存中取而不去数据库查询,对于想要实时数据时这并不友好。在百度后解决方案如直:classTodayRecordView(viewsets.ModelViewSet):serializer_class=OrderRecordSerializerpagination_c......
  • MFC视频教学第一课,做一个简单的界面,理解应用程序和操作系统之间的消息传递机制
     #include<windows.h>#include<stdio.h>LRESULTCALLBACKWinSunProc(HWNDhwnd,UINTuMsg,WPARAMwParam,LPARAMlParam);//WinMain是操作系统调用的,是系统的入口函数intWINAPIWinMain(HINSTANCEhInstance,//当前应运程序......