SPI是什么
SPI的英文名称是Service Provider Interface,是Java 内置的服务发现机制。
在开发过程中,将问题进抽象成API,可以为API提供各种实现。如果现在需要对API提供一种新的实现,我们可以不用修改原来的代码,直接生成新的Jar包,在包里提供API的新实现。通过Java的SPI机制,可以实现了框架的动态扩展,让第三方的实现能像插件一样嵌入到系统中。
Java的SPI类似于IOC的功能,将装配的控制权移到了程序之外,实现在模块装配的时候不用在程序中动态指明。所以SPI的核心思想就是解耦,这在模块化设计中尤其重要。
SPI实现步骤
1、写一个接口定义一些方法。如下
SPIService 接口 :
package com.example.orm.modules.spi;
/**
* @description:
* @create: 2020-08-03 09:25
**/
public interface SPIService {
void test();
}
实现类:
package com.example.orm.modules.spi;
/**
* @description:
* @create: 2020-08-03 09:26
**/
public class SPIServiceImpl implements SPIService {
@Override
public void test() {
System.out.println("hello i'm from SPIServiceImpl");
}
}
package com.example.orm.modules.spi;
/**
* @description:
* @create: 2020-08-03 09:27
**/
public class SPIServiceImpl2 implements SPIService {
@Override
public void test() {
System.out.println("hello i'm from SPIServiceImpl2");
}
}
2、实现该接口。
3、在src/main/java/resource 目录下创建META-INF/services目录,并以上述接口全限定名为名称创建文件。文件中写入实现类的全限定名。如下:
4、测试:
ServiceLoader<SPIService> spiServices = ServiceLoader.load(SPIService.class);
Iterator<SPIService> iterator = spiServices.iterator();
while(iterator.hasNext()) {
SPIService next = iterator.next();
next.test();
}
结果:
hello i'm from SPIServiceImpl
hello i'm from SPIServiceImpl2
Process finished with exit code 0
应用场景
比较常见的应用场景:
JDK提供一个数据库驱动接口类,JDBC加载不同的数据库驱动实现类
日志门面接口实现类加载,SLF4J加载不同厂商提供的日志实现类。
Druid 中大量使用了SPI,但是对它做出了延伸扩展。利用filterChain配置扩展。
优缺点
优点:
使用Java SPI机制的优势是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。
相比使用提供接口jar包,供第三方服务模块实现接口的方式,SPI的方式使得源框架,不必关心接口的实现类的路径,可以不用通过下面的方式获取接口实现类:
- 代码硬编码import 导入实现类
- 指定类全路径反射获取:例如在JDBC4.0之前,JDBC中获取数据库驱动类需要通过Class.forName("com.mysql.jdbc.Driver"),类似语句先动态加载数据库相关的驱动,然后再进行获取连接等的操作
- 第三方服务模块把接口实现类实例注册到指定地方,源框架从该处访问实例
通过SPI的方式,第三方服务模块实现接口后,在第三方的项目代码的META-INF/services目录下的配置文件指定实现类的全路径名,源码框架即可找到实现类
缺点:
- 虽然ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是接口的实现类全部加载并实例化一遍。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过Iterator形式获取,不能根据某个参数来获取对应的实现类。
- 多个并发多线程使用ServiceLoader类的实例是不安全的。