SPI,全称是Service Provider Interface ,是java6引入的一种基于ClassLoader来发现并加载服务的机制,一个标准的SPI由3个组件构成,分别是:
1,Service, 即自定义的接口或抽象类
2,Service Provider,即Service接口的实现类
3,ServiceLoader,即java.util.ServiceLoader类,负责加载Service Provider
一,SPI示例:
1,定义一个接口,即Service
package com.xx;
public interface MyInterface {
}
2,定义一个或多个实现类,即Service Provider
package com.xx;
public class MyInterfaceImplA implements MyInterface{
}
package com.xx;
public class MyInterfaceImplB implements MyInterface{
}
3,建立配置文件
如下,如果是非maven项目,配置文件放在 src/META-INF/services目录下;
如果是maven项目,配置文件放在 resources/META-INF/services目录下;
文件名是接口的全限定名,文件内容是实现类的全限定名,如果有多个实现类每个占一行
4, 获取实现类
public class TestSPI {
public static void main(String[] args) {
ServiceLoader<MyInterface> serviceLoader = ServiceLoader.load(MyInterface.class);
Iterator<MyInterface> iterator = serviceLoader.iterator();
while (iterator.hasNext()){
MyInterface next = iterator.next();
System.out.println(next);
}
}
}
输出结果如下:
可以看到已经创建了2个实现类的实例。
二, SPI的三大规范要素:
1,配置文件的规范
1.1,配置文件路径:必须在工程或JAR包中的META-INF/services目录下
1.2,配置文件名称:Service接口的全限定名
1.3,配置文件内容:Service实现类(即Service Provider类)的全限定名,如果有多个实现类,每个实现类在文件中独占一行
2,Service Provider类必须有无参的构造方法
3,保证能加载到配置文件和Service Provider类
3.1 将Service Provider类的jar包放到classpath中
3.2 将jar包安装到jre的扩展目录中
3.3 自定义一个ClassLoader
三,SPI的作用?
我的理解:提供了一种约定俗成的方式,能根据接口类名找它的实现类并创建实现类对象,可用于实现各种插件或者灵活替换框架所使用的组件,也可减少框架使用者对框架的配置。
四,SPI的优点?
面向接口编程+配置文件+反射技术,优雅的实现模块之间的解耦
五,SPI应用场景
JDBC 、SLF4J、Servlet容器初始化、另外springboot的自动配置借鉴了SPI的思想。
5.1 JDBC中SPI的使用
在SPI出现之前,使用Class.forName加载数据库驱动:
程序员需要记住不同数据库的驱动类名称,通过SPI机制就不需要记住了:
因为JDBC驱动接口的全限定名是java.sql.Driver,各个数据库厂商都是实现此接口,
因此各厂商可以在自己的jar包META-INF/services目录下创建一个java.sql.Driver的文件,里面写上自己的实现类名称,这样,程序员在使用数据库驱动jar包时,只需引入这个jar包即可,不用关心其实现类是哪个,程序启动时会自动创建java.sql.Driver文件中的类。
下图是mysql的驱动jar解压之后的内容,里面确实有java.sql.Driver文件,如下:
5.2 springboot借鉴SPI思想产生了spring Factories 机制,实现了自动配置:
spring Factories 机制的配置文件叫 spring.factories(类似SPI的配置文件),放置在META-INF下,文件内容的格式是
接口全限定名 = 实现类全限定名1,实现类全限定名2
例如springboot自动加载mybatis原理如下: