什么是 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 并不是线程安全的.
参考
- https://www.jianshu.com/p/46b42f7f593c
- https://www.cnblogs.com/jy107600/p/11464985.html
来源: https://www.cnblogs.com/54chensongxia/p/12372027.html