Java SPI 居然这么多知名框架在用

2020-08-17 10:55:57 浏览数 (1)

1. 前言

Java SPI全称Java Service Provider Interface。是 Java 提供的一套用来被第三方实现或者扩展的 API,它可以用来启用框架扩展和替换组件。你可以理解为接口的自动注册发现,它的应用其实非常广泛,微服务通讯组件Dubbo、规则引擎Apache Camel、敏捷Java开发框架Spring BootJDBC 规范都用到了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>,因为如果下游厂商编写了多个实现并注册也要保证能获取到,当然这也有一定的弊端,后面会谈这个事情。针对上面的接口发现逻辑是非常简单的:

代码语言:javascript复制
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注册了。步骤是这样的:

  1. classpath下建立META-INF/services目录,
  2. 在 1 步骤建立的META-INF/services目录下创建名称为规范抽象接口(例如上面的cn.felord.spi.ApiService)的文件。
  3. 在文件里把对应文件名称抽象接口的实现类的全限定名写进去,有多个的话换行。

SPI实现注册

这样就下游就开发完毕了。同样提供了一个包给调用方去调用;

代码语言:javascript复制
<dependency>
    <groupId>cn.felord</groupId>
    <artifactId>provider</artifactId>
    <version>1.0</version>
</dependency>

3.3 调用 SPI 实现

消费方调用只需要集成规范的实现去调用发现接口的逻辑对象ApiDiscovery就可以了:

代码语言:javascript复制
<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是一个非常细心的过程,如果我们不小心配置错误就无法达到预期。所以谷歌提供了一个AutoServiceSPI增强包,有兴趣的话可以去了解一下,非常简单,这里不再讲解。相关 DEMO 可通过本公众号回复spi获取。多多关注:码农小胖哥 更多干货分享。

0 人点赞