SPI机制 1
说到SpringBoot的自动配置机制,就不能不谈Java的SPI(service provider interface)机制,SPI是JDK内置的一种服务发现机制,它主要是让Java程序在运行时再去发现服务的实现类。就拿JDBC来说,每一个数据库厂商的数据库产品暴露给外部的访问方式、传输协议都可能不一致,所以Java索性就只指定一个标准,各个厂商自己根据这个标准来实现对自己数据库产品的驱动,比如MySQL 有 MySQL connector,Oracle有odbc6 等等。
SPI的组成部分:
interface
:需要使用SPI机制的接口,放在集成方,表明我只提供这个规范,怎么实现那是厂商或者其他框架的事情,比如JDBC 的java.sql.Driver
config
:需要在实现方的包下建立/META-INF/services
文件夹,并在这个文件夹下添加当前库提供了哪个SPI接口的实现,比如mysql connector包下 就有一个文件命名为java.sql.Driver,文件命名必须和实现的接口同名
implements
:这个是对上面说的接口的实现,可以有多个,并将实现类的全限定名在上述文件中一行写一个
模拟
咱们模拟组装电脑时候的CPU,CPU厂商有很多,Intel、AMD等,我们就抽象一下CPU里面有个compute()方法。
package com.xhulife.spi;
public interface CPU {
void compute();
}
就当咱们买了一堆零件,然后就差CPU了,我们先准备一个AMD的CPU:
package com.xhulife.spi.cpu;
import com.xhulife.spi.CPU;
public class AmdCPU implements CPU{
@Override
public void compute() {
System.out.println("this is amdCPU compute");
}
}
并且在resources/META-INF/services/com.xhulife.spi.CPU
下写入:
com.xhulife.spi.cpu.AmdCPU
然后打成jar包,放到我们的电脑
上(放到classpath里面),电脑上需要写一段代码来加载CPU:
package com.xhulife.spi;
import java.util.ServiceLoader;
public class Main {
public static void main(String[] args) {
ServiceLoader<CPU> load = ServiceLoader.load(CPU.class);
for (CPU c:load){
c.compute();
}
}
}
打开电脑:
this is amdCPU compute
突然某一天,我想换一个Intel I9处理器了:
package com.xhulife.spi.cpu;
import com.xhulife.spi.CPU;
public class IntelCPU implements CPU {
@Override
public void compute() {
System.out.println("this is Intel I9 compute");
}
}
把之前的AMD处理器拿出来,然后把新的I9处理器打包放到电脑
上,开机:
this is Intel I9 compute
这就模拟了整个流程
应用场景
SPI机制其实就是用来指定规范的,我把规范制定好,你们大家如果想在我的平台上大展拳脚,那就根据我的规范来写一套标准的操作集合。其实就拿JDBC来说,没有JDK制定的jdbc规范可以吗?肯定是可以的,不过这就苦了我们程序员,我们得根据各个数据库厂商的数据传输协议,自己写一个驱动,或者是各个数据库厂商根据各自的数据库特性,自己搞一套驱动方案,这就给我们软件开发带来了更大的挑战。
但是这个机制似乎对我们一般人没什么大的作用,我们一不会去自己根据JDBC规范来写某一个数据库的驱动,二不会发布一个规范让别人来遵守并且实现(有这一天的话,我就要上天了)。只是我们需要了解有这样一个机制,当我们在企业开发的时候如果遇到类似的需求,或许有用武之地
双亲委派模型
Java类加载器可以分为2类:引导类加载器和其他加载器,这么分的主要原因是引导类加载器是包含在Java虚拟机中的,在java程序中无法引用这个类加载器,其他加载器又可以分为java扩展类加载器和应用加载器,扩展加载器负责加载lib/ext 下的包,应用加载器负责加载我们自己写的java程序或者第三方jar包。
java的设计人员推荐使用双亲委派模型来加载java类,即当类加载器接收到一个加载类的请求之后,先委派给父加载器去加载(注意这里父加载器并不是通过继承,而是通过组合来的),父加载器反馈无法加载这个类,自己再尝试加载,所以java中越是基础的类,加载其的类加载器越是上层
双亲委派模型被破坏
Java 类的加载使用双亲委派模型,SPI是JDK内置的服务发现机制,很多SPI接口是由java的核心库提供的,这些接口是由引导类加载器(Bootstrap Classloader)加载的,但是这些类在加载的时候,又需要调用SPI实现类,在引导类加载器的搜索范围内,无法搜索到有各个厂商实对SPI接口的实现。这样就无法完成类的加载,Java的设计者提供了一种不太优雅的实现方式,线程上下文类加载器(Thread Context Classloader),这个类加载器是可以设置的,新建线程的时候会从父线程继承,如果没有设置过,那么就是应用类加载器(Application Classloader),有了这个类加载器,就可以逆向加载SPI接口的实现类