什么是 SPI
SPI是Service Provider Interface的简称,是JDK默认提供的一种将接口和实现类进行分离的机制。这种机制能将接口和实现进行解耦,大大提升系统的可扩展性。
SPI机制约定:当一个Jar包需要提供一个接口的实现类时,这个Jar包需要在META-INF/services/目录里同时创建一个以服务接口命名的文件。该文件里就是实现该服务接口的具体实现类。而当外部程序装配这个模块的时候,就能通过该Jar包META-INF/services/里的配置文件找到具体的实现类名,并装载实例化,完成模块的注入。
比如下面的列子,jcl-over-slf4j
这个Jar包提供了conmon-logging中LogFactory
这个接口的实现。
文件中的内容如下:
# 这里表名具体的实现类是`org.apache.commons.logging.impl.SLF4JLogFactory`这个类
org.apache.commons.logging.impl.SLF4JLogFactory
# Axis gets at JCL through its own mechanism as defined by Commons Discovery, which
# in turn follows the instructions found at:
# http://java.sun.com/j2se/1.3/docs/guide/jar/jar.html#Service Provider
JDK为了方便查找服务的实现,还提供了一个工具类:java.util.ServiceLoader。
ServiceLoader<Object> loader = ServiceLoader.load(LogFactory);
loader.forEach((item)->{
System.out.println(item);
});
上面代码中使用ServiceLoader
遍历使用SPI机制提供的所有LogFactory
实现。
应用场景
SPI机制的主要应用有框架扩展和组件的替换等,比如
- JDBC接口实现类的运行时加载:我们连接具体的数据库是都需要添加相关的Jar包依赖,但是不需要我们再做任何其他配置,只要将Jar包放到classpath下就行了。这是一个最常见的SPI应用场景。
- 日志门面加载具体的日志实现类:之前的博客中介绍到,jcl和slf4j等只是日志实现类,Log4j和LOgBack才是具体的日志实现。JCL和SLF4J加载日志实现类时也使用了SPI机制,具体请看上面章节中举的列子。
- Spring中大量使用了SPI:比如对servlet3.0规范对ServletContainerInitializer的实现、自动类型转换Type Conversion SPI(Converter SPI、Formatter SPI)等
自己实现
下面就一步步从定义接口到提供SPI实现类来演示下SPI机制具体的使用方式。
step1:先定义一个接口
public interface SaySomething {
String say(String name);
}
step2:编写实现类
public class ASaySomething implements SaySomething {
@Override
public String say(String name) {
return "Hi,"+name+", l am A...";
}
}
step3:在resource下添加META-INFO/services目录
添加完这个目录后,添加一个以SaySomething
接口的全限定名为名字的文件,这个文件的内容是你要设置的具体实现类。这边我们就设置实现类为上面的ASaySomething
。
step4:使用SPI机制
public static void main(String[] args) {
ServiceLoader<SaySomething> loader = ServiceLoader.load(SaySomething.class);
loader.forEach(item ->{item.say("csx");});
}
API和SPI的比较
在开发中我们还经常会提到API这个名词,下面也总结下两者的区别:
-
API (Application Programming Interface)在大多数情况下,都是实现方制定接口并完成对接口的实现,调用方仅仅依赖接口调用,且无权选择不同实现。 从使用人员上来说,API 直接被应用开发人员使用。
-
SPI (Service Provider Interface)是调用方来制定接口规范,提供给外部来实现,调用方在调用时则选择自己需要的外部实现。 从使用人员上来说,SPI 被框架扩展人员使用。
优缺点
优点
- 使用Java SPI机制的优势是实现解耦,使得第三方服务模块的装配控制的逻辑与调用者的业务代码分离,而不是耦合在一起。应用程序可以根据实际业务情况启用框架扩展或替换框架组件
缺点
- SPI必须先将接口的所有实现类都遍历出来才能最后选择具体使用哪个类。有些不要的类也会被实例化,可能会比较浪费内存。
ServiceLoader
并不是线程安全的。