SPI,全称 Service Provider Interface,是Java中提供的一种服务发现机制
它允许应用程序动态地加载和使用第三方提供的服务实现,而无需在代码中引用这些实现类。
Java SPI是基于接口编程思想的具体体现,通过将服务接口和其实现分离,从而具备更好的可扩展性和可维护性
如何定义一个Java SPI
1.定义一组接口
public interface MyService {
void doSomething();
}
2.提供一个或多个实现了该接口的实现类,作为服务提供者
public class MyServiceProvider1 implements MyService {
public void doSomething() {
System.out.println("Provider 1 is doing something");
}
}
public class MyServiceProvider2 implements MyService {
public void doSomething() {
System.out.println("Provider 2 is doing something");
}
}
3.配置文件
在 src/main/resources下新建META-INF/services目录,同时新增一个以接口的全限定名com.example.MyService命名的文件,内容是要应用的实现类的全限定名
注意:每个实现类的全限定名占据一行
com.example.MyServiceProvider1
com.example.MyServiceProvider2
4.使用ServiceLoader来加载该接口下的服务提供者
ServiceLoader<MyService> loader = ServiceLoader.load(MyService.class);
for (MyService service : loader) {
service.doSomething();
}
//for循环等价于如下代码
Iterator var2 = load.iterator();
while(var2.hasNext()){
MyService service=(MyService)var2.next();
service.doSomething();
}
执行结果
1.Provider 1 is doing something
2.Provider 2 is doing something
实现原理
首先看下ServiceLoader类的几个重要的成员变量
结合源码,总结 Java SPI实现的流程如下:
1.调用ServiceLoader.load()方法会先创建一个新的ServiceLoader,并实例化类中的成员变量
2.调用iterator()方法获取一个迭代器对象:
2.1 ServiceLoader会先判断providers对象中是否有缓存实例对象,如果存在则直接返回
2.2 如果不存在,则执行类的加载操作:
2.2.1 读取 META-INF/services/+指定接口的全限定名下的配置文件,获取所有能被实例化的类的全类限定名
2.2.2 通过反射加载并实例化对象并放入providers缓存中
2.2.3 返回该实例对象
SPI的应用场景
1.数据库驱动程序加载
JDBC为了实现可插拔的数据库驱动,在Java.sql.Driver 接口中定义了一组标准的API规范,而具体的数据库厂商则需要实现这个接口,以提供自己的数据库驱动程序。
在Java中,JDBC驱动程序的加载就是通过SPI机制实现的
2.日志框架的实现
流行的开源日志框架,如Log4j、SLF4J和Logback等,都采用了SPI机制。用户可以根据自己的需求选择合适的日志实现,而不需要修改代码
3.Srping框架
Spring框架中的Bean加载机制就使用了SPI思想,通过读取classpath下的META-INF/spring.factories文件来加载各种自定义的Bean
4.Dubbo框架
Dubbo框架也使用了SPI思想,通过接口注解@SPI声明扩展点接口,并在classpath下的META-INF/dubbo目录中提供实现类的配置文件,来实现扩展点的动态加载