一般情况下,我们会通过 API 对外提供服务。这里,API 提供服务的接口的逻辑是固定的,换句话说,它具有通用性。但是,但我们遇到具有类似的业务逻辑的场景时,即核心的主干逻辑相同,而细节的实现略有不同,那我们该何去何从?很多时候,我们会选择提供多个 API 接口给不同的业务方使用。事实上,我们可以通过 SPI 扩展点来实现的更加优雅。什么是 SPI?SPI 的英文全称是 Serivce Provider Interface,即服务提供者接口,它是一种动态发现机制,可以在程序执行的过程中去动态的发现某个扩展点的实现类。因此,当 API 被调用时会动态加载并调用 SPI 的特定实现方法。
此时,你是不是联想到了模版方法模式。模板方法模式的核心思想是定义骨架,转移实现,换句话说,它通过定义一个流程的框架,而将一些步骤的具体实现延迟到子类中。事实上,在微服务的落地过程中,这种思想也给我们提供了非常好的理论基础。
现在,我们来看一个案例:电商业务场景中的未发货仅退款。这种情况在电商业务中非常场景,用户下单付款后由于各种原因可能就申请退款了。此时,因为不涉及退货,所以只需要用户申请退款并填写退款原因,然后让卖家审核退款。那么,由于不同平台的退款原因可能不同,我们可以考虑通过 SPI 扩展点来实现。
我们先来看下 JDK 对 SPI 机制的支持。在面向对象编程的设计中,我们会采取面向接口编程的方式。
代码语言:javascript复制public interface IRefundSeason {
default List<String> invoke(){
// 1. 前置业务
// 。。。
// 2. 获取退款原因列表
List<String> refundSeasons = getRefundSeasonList();
// 3. 后置业务
// 。。。
return refundSeasons;
}
List<String> getRefundSeasonList();
}
这里,我们在简单地定义两个实现类。
代码语言:javascript复制public class RefundSeason1 implements IRefundSeason{
@Override
public List<String> getRefundSeasonList() {
return ImmutableList.of("商品降价","商品无货","其他");
}
}
public class RefundSeason2 implements IRefundSeason{
@Override
public List<String> getRefundSeasonList() {
return ImmutableList.of("不想要了","七天无理由","其他");
}
}
服务提供者提供接口的一种实现后,我们需要在 META-INF/services 目录中创建一个以接口全限定名的文件 com.lianggzone.design.template_method.example.spi.IRefundSeason
。
这里,文件地内容为实现类的全限定名。
代码语言:javascript复制com.lianggzone.design.template_method.example.spi.RefundSeason1
最后,我们通过测试代码来验证下功能。
代码语言:javascript复制public class RefundSeasonTest {
public static void main(String[] args) {
ServiceLoader<IRefundSeason> loader = ServiceLoader.load(IRefundSeason.class);
loader.forEach(i -> {
List<String> refundSeasons = i.invoke();
System.out.println(refundSeasons);
});
}
}
至此,我们实现了一个简单地 Java 的 SPI 功能。事实上,Java 中的 SPI 实现非常简单,我们可以阅读 java.util.ServiceLoader
类。
注意的是,ServiceLoader 每次加载都会生成一份实例,且只能遍历获取所有接口实例,非常浪费资源。同时,获取实现类不够灵活,不能根据某个参数获取对应的实现类,且不支持排序,会出现排序不稳定的情况。因此,很多框架为了解决以上的问题,重新实现了一套更强大的 SPI 机制。例如,Dubbo SPI 自定义了一套 SPI 机制,并把所需的配置文件需放置在 META-INF/dubbo 路径下。与 Java SPI 实现类配置不同,Dubbo SPI 是通过键值对的方式进行配置,这样可以按需加载指定的实现类。Dubbo SPI 源码分析,参见 http://dubbo.apache.org/zh-cn/docs/sourcecodeguide/dubbo-spi.html