一文读懂SPI机制
作者:互联网
一文读懂SPI机制
目录1、问题
什么是SPI?
2、答案
- 要给出名词解释:SPI全称为:Service Provider Intreface,直驿为服务提供者接口,它是JDK里面内置的一种动态扩展的一个实现(这里顺便提一句,也是打破JVM规范中的双亲委派机制的实现之一,因为它是由上层加载器加载下层具体实现的一个不优雅的实现方案)
- 原理:简单来说,我们可以定义一个标准的接口,然后按照一定规则提供给第三方使用,第三方库里面可以去实现这个接口,那么我们在程序运行的时候,通过自己定义的规则(多指配置信息),去加载第三方库对这个接口的实现,从而去完成功能的一个动态扩展
有原理一定有案例实现才算完整
SPI机制非常典型的一个应用案例就是数据库的一个驱动:java.jdbc.Driver
。
它是一个接口,而jdk并没有提供这样一个实现,具体的实现是由数据库的厂商来完成的。
优点:SPI这种方式可以很好的解决不同框架之间的扩展问题**
3、那么,底层如何完成呢?
第三方在/META-INF/services
下,以要实现的SPI接口为文件名,具体的实现类为文件内容,创建一个配置文件。
Java中ServiceLoader
会去加载配置文件,并加载对应的具体实现类。
如果同一个SPI接口有多个实现,可以通过ServiceLoader.iterator去遍历获取所有具体实现类。
在一个项目中,引入mysql-connector-java.jar
实现,然后在自定义一个实现了 java.sql.Driver
SPI接口的 com.mybatis.demo.spi.DriverImpl
实现类,并在resources
下,定义好/META-INF/services/java.sql.Driver
文件,其内容为DriverImpl
的全限定名,这样我们就有了Driver实现。
public static void main(String[] args) {
ServiceLoader<Driver> load = ServiceLoader.load(Driver.class);
load.iterator().forEachRemaining(action -> {
System.out.println(action.getClass());
});
}
输出结果如下:
class com.mybatis.demo.spi.DriverImpl
class com.mysql.cj.jdbc.Driver
4、SPI实现类的类加载器是什么?
ServiceLoader通过线程上下文获取加载实现类的classloader,一般情况下是 application classloader,当然也可以自定义class loader。
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
public static <S> ServiceLoader<S> load(Class<S> service,
ClassLoader loader)
{
return new ServiceLoader<>(service, loader);
}
Connect
是jdk中定义的一个SPI接口,我们以mysql-connector-java.jar
为具体实现类引入classpath下,然后建立连接,看看各个类对应的实际类加载器。
public static void main(String[] args) throws SQLException {
Connection connection =
DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test?useSSL=false", "root", "123456");
System.out.println(connection.getClass().getClassLoader());
System.out.println(connection.getClass().getClassLoader().getParent());
System.out.println(connection.getClass().getClassLoader().getParent().getParent());
System.out.println(java.sql.Connection.class.getClassLoader());
System.out.println(String.class.getClassLoader());
}
输出结果
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@3ab39c39
null
null
null
5、SPI是否有破坏双亲委派模型?
以Connection和mysql-connect-java.jar为例,Bootstrap本身就没有办法加载在classpath下的mysql-connector-java.jar,所以虽然Connection
是定义在rt.jar中,且其本身由Bootstrap ClassLoader加载,但是具体的实现类,Bootstrap ClassLoader是无法加载的。
我的理解是,首先双亲委派模型,本身并不是强制的,双亲委派模型保证了”安全”(越基础的类由越基础的类加载器加载,且只会被加载一次),但是却不够灵活。
如果加载时拿到的class loader是application class loader,那么是没有破坏双亲委派模型的;但是如果拿到的是自定义的class loader,且自定义class loader没有遵守双亲委派模型,那么就SPI就破坏了双亲委派模型。
所以SPI给破坏双亲委派模型留了口子,但是具体有没有破坏,还是要看实际加载的class loader。
另一种看法
当然,也有另一种说法是,SPI中,接口是由Bootstrap ClassLoader 加载的,具体的实现类却是由当前线程上下文的ClassLoader(一般是Application ClassLoader)加载的,而基于双亲委派的可见性原则(子类加载器可以看到父类加载器,父类加载器看不到子类加载器),SPI 调用方无法看到或拿到具体的实现类的。
双亲委派模型中,class loader的可见性
6、SPI机制的变种
Spring的自动装配原理
在一个Spring的项目中,肯定会依赖一些其他框架比如:Mybatis,而Spring默认会把当前包及其子包下的bean注入到ioc中,而其他框架由于包名不同,所以不能通过扫描注入,而且注入过程中要尽量与项目解耦,为了解决这一问题,Spring参考了这一SPI机制的设计思想,规定在classpath
目录下META-INF
文件中可以定义spring.factories
文件,这样在项目启动时可以加载所有jar包中的所有spring.factories
文件,将定义的类注入到ioc中,SPI机制在其他框架中也有很多应用,如:Dubbo、Slf4j等
标签:一文,读懂,实现,loader,SPI,ServiceLoader,class,加载 来源: https://www.cnblogs.com/lishanbiaosMark/p/16321668.html