一、SPI机制简介
1、什么是SPI机制
SPI(Service Provider Interface 的简称)字面意思为服务提供者接口,它是 JDK内置的一种服务提供发现机制(JDK提供给“服务提供厂商”或者“插件开发者”使用的接口)。
SPI是一种动态替换发现的扩展机制制,一种解耦非常优秀的思想。它可以很灵活的让接口和实现分离, 让api提供者只提供接口, 第三方来实现。
SPI机制的主要目的:
- 为了解耦,将接口和具体实现分离开来;
- 提高框架的扩展性。
1.1 Java SPI规范
定义服务的通用接口,针对通用的服务接口,提供具体的实现类。
- 在jar包(服务提供者)的
META-INF/services/目录
中,新建一个文件,文件名为SPI接口的"全限定名"
。- 文件内容为该接口的
具体实现类的"全限定名"
。
- 将spi所在jar放在主程序的classpath中。
- 服务调用方使用
java.util.ServiceLoader
去动态加载具体的实现类到JVM中。
Java SPI的工作原理:
就是ClassPath路径下的 META-INF/services文件夹中, 以接口的全限定名来命名文件名,文件里面写该接口的实现。然后再资源加载的方式,读取文件的内容(接口实现的全限定名), 然后再去加载类这些实例类并实例化。
2、优缺点
优点:
- 使用Java SPI机制的优势是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离。应用程序可以根据实际业务情况启用框架扩展或替换框架组件。
缺点:
- 虽然 ServiceLoader也算是使用的延迟加载,但是基本只能通过遍历全部获取,也就是
接口的实现类全部加载并实例化一遍
。如果你并不想用某些实现类,它也被加载并实例化了,这就造成了浪费。获取某个实现类的方式不够灵活,只能通过 Iterator形式获取,不能根据某个参数来获取对应的实现类。 - 多个并发多线程使用ServiceLoader类的实例是不安全的。
二、如何使用Java的SPI
1、自定义接口和实现类
1.1 接口
public interface MyLog {
void debug();
}
1.2 实现类
这里定义两个实现类。
public class MyLog4j implements MyLog {
@Override
public void debug() {
System.out.println("==========MyLog4j");
}
}
public class MyLogback implements MyLog {
@Override
public void debug() {
System.out.println("==========MyLogback");
}
}
2、创建配置文件
在项目resources目录下新建一个 META-INF/services文件夹
。
- 在 META-INF/services/目录下,创建一个文件,
文件名为该SPI接口的全限定名
。 - 文件内容是具体实现类的全限定名,如果有多个,则用分行符分隔。
3、测试,加载实现类
在 代码中通过使用 java.util.ServiceLoader
来动态加载具体的实现类并实例化到JVM中。
public class MySpiTest {
public static void main(String[] args) {
//加载META-INF/services 下面的MyLog文件中的所有实现类
ServiceLoader<MyLog> serviceLoader = ServiceLoader.load(MyLog.class);
//遍历所有实现类
Iterator<MyLog> iterator = serviceLoader.iterator();
while (iterator.hasNext()) {
MyLog next = iterator.next();
if(next instanceof MyLog) {
next.debug();
}else if(next instanceof MyLogback) {
next.debug();
}else {
System.out.println("其他");
}
}
}
}
注意:
JDK SPI会一次性实例化扩展点的所有实现。
参考文章:
– 求知若饥,虚心若愚。