Java SPI全称Java Service Provider Interface。是 Java 提供的一套用来被第三方实现或者扩展的 API,它可以用来启用框架扩展和替换组件。你可以理解为接口的自动注册发现,它的应用其实非常广泛,微服务通讯组件Dubbo、规则引擎Apache Camel、敏捷Java开发框架Spring Boot、JDBC 规范都用到了SPI机制。接下来我们通过一个DEMO来认识它。
2. SPI 的需求场景
假如你是一家产品上游企业,你希望你的厂商都能按照你的标准去生产下游组件,所以你制定了一个驱动规范。厂商只要按照你的规范去实现驱动,那么就能对接上你的应用产品。所以SPI的开发流程大致是这样的:
Java SPI 开发流程
试想一下 JDBC规范不就是这样的吗?
3. SPI 开发
按照上面的需求场景,我们来模拟一下SPI开发流程。
3.1 上游厂商需要做的事情
制定接口规范,对于Java来说就是抽象出一些接口并提供给下游厂商。这里简单写一个接口:
代码语言:javascript复制/**
* @author felord.cn
* @since 9:55
**/
public interface ApiService {
void execute();
}
然后编写接口的发现相关逻辑,Java提供了java.util.ServiceLoader<S>
来进行SPI接口的加载,非常简单。需要指出的是它实现了可迭代接口Iterable<E>
,因为如果下游厂商编写了多个实现并注册也要保证能获取到,当然这也有一定的弊端,后面会谈这个事情。针对上面的接口发现逻辑是非常简单的:
import java.util.ServiceLoader;
/**
* @author felord.cn
* @since 13:06
**/
public class ApiDiscovery {
public static void doDiscovery(){
ServiceLoader<ApiService> load = ServiceLoader.load(ApiService.class);
load.forEach(ApiService::execute);
}
}
这样上游的事就干完了,打成 jar 生成依赖提供给下游去实现。
代码语言:javascript复制<dependency>
<groupId>cn.felord</groupId>
<artifactId>service-api</artifactId>
<version>1.0</version>
</dependency>
其中发现这一部分也可以不提供给下游。
3.2 下游厂商需要做的事情
下游厂商集成上游厂商提供的依赖:
代码语言:javascript复制<dependency>
<groupId>cn.felord</groupId>
<artifactId>service-api</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
开始实现接口规范:
代码语言:javascript复制import cn.felord.spi.ApiService;
/**
* @author felord.cn
* @since 10:24
**/
public class SpiProvider implements ApiService {
@Override
public void execute() {
System.out.println(" hello provider ");
}
}
实现完成之后就要进行SPI注册了。步骤是这样的:
- 在classpath下建立
META-INF/services
目录, - 在 1 步骤建立的
META-INF/services
目录下创建名称为规范抽象接口(例如上面的cn.felord.spi.ApiService
)的文件。 - 在文件里把对应文件名称抽象接口的实现类的全限定名写进去,有多个的话换行。
SPI实现注册
这样就下游就开发完毕了。同样提供了一个包给调用方去调用;
代码语言:javascript复制<dependency>
<groupId>cn.felord</groupId>
<artifactId>provider</artifactId>
<version>1.0</version>
</dependency>
3.3 调用 SPI 实现
消费方调用只需要集成规范的实现去调用发现接口的逻辑对象ApiDiscovery
就可以了:
<dependency>
<groupId>cn.felord</groupId>
<artifactId>provider</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>cn.felord</groupId>
<artifactId>service-api</artifactId>
<version>1.0</version>
</dependency>
4. SPI 的优缺点
SPI的优点:实现了解耦,不再通过import
关键字导入实现,而是通过动态的加载实现。使得业务组件插件化。
SPI的缺点:ServiceLoader在加载实现类的时候会全部加载并实例化,无论你是否需要使用到它。而且只能通过迭代去获取实现,无法通过关键字来获取。
5. 总结
今天通过一个简单的例子演示了Java SPI特性的使用,这对开发驱动、模块化、插件化非常有用。配置注册SPI是一个非常细心的过程,如果我们不小心配置错误就无法达到预期。所以谷歌提供了一个AutoService的SPI增强包,有兴趣的话可以去了解一下,非常简单,这里不再讲解。相关 DEMO 可通过本公众号回复spi获取。多多关注:码农小胖哥 更多干货分享。