1 什么是SPI
SPI 全称Service Provider Interface。面向接口编程中,我们会根据不同的业务抽象出不同的接口,然后根据不同的业务实现建立不同规则的类,因此一个接口会实现多个实现类,在具体调用过程中,指定对应的实现类,当业务发生变化时会导致新增一个新的实现类,亦或是导致已经存在的类过时,就需要对调用的代码进行变更,具有一定的侵入性。
整体机制图如下:

Java SPI 实际上是“基于接口的编程+策略模式+配置文件”组合实现的动态加载机制。
2 SPI在京喜业务中的使用
2.1 简介
目前仓储中台和京喜BP的合作主要通过SPI扩展点的方式。好处就是对修改封闭、对扩展开放,中台不需要关心BP的业务实现细节,通过对不同BP配置扩展点的接口来达到个性化的目的。目前京喜BP主要提供两种方式的接口实现,一种是jar包的方式,一种是提供jsf接口。
下边来分别介绍下两种方式的定义和实现。
2.2 jar包方式
2.2.1 说明及示例
扩展点接口继承IDomainExtension,这个接口是dddplus包中的一个插件化接口,实现类要使用Extension(io.github.dddplus.annotation)注解,标记BP业务方和接口识别名称,用来做个性化的区分实现。
以在库库存盘点扩展点为例,接口定义在调用方提供的jar中,定义如下:- public interface IProfitLossEnrichExt extends IDomainExtension {
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:jsf="http://jsf.jd.com/schema/jsf"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://jsf.jd.com/schema/jsf
- http://jsf.jd.com/schema/jsf/jsf.xsd"
- default-lazy-init="false" default-autowire="byName">
- <jsf:consumer id="skuQueryService" interface="com.jdwl.wms.masterdata.api.sku.SkuQueryService"
- alias="${jsf.consumer.masterdata.alias}" protocol="jsf" check="false" timeout="10000" retries="3"/>
- </beans>@Valid
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:jsf="http://jsf.jd.com/schema/jsf"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://jsf.jd.com/schema/jsf
- http://jsf.jd.com/schema/jsf/jsf.xsd"
- default-lazy-init="false" default-autowire="byName">
- <jsf:consumer id="skuQueryService" interface="com.jdwl.wms.masterdata.api.sku.SkuQueryService"
- alias="${jsf.consumer.masterdata.alias}" protocol="jsf" check="false" timeout="10000" retries="3"/>
- </beans>@Comment({"批量盘盈亏数据丰富扩展", "扩展的属性请放到对应明细的 extendContent.extendAttr Map字段中:profitLossBatchDetail.putExtendAttr(key, value)"})
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:jsf="http://jsf.jd.com/schema/jsf"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://jsf.jd.com/schema/jsf
- http://jsf.jd.com/schema/jsf/jsf.xsd"
- default-lazy-init="false" default-autowire="byName">
- <jsf:consumer id="skuQueryService" interface="com.jdwl.wms.masterdata.api.sku.SkuQueryService"
- alias="${jsf.consumer.masterdata.alias}" protocol="jsf" check="false" timeout="10000" retries="3"/>
- </beans>List<ProfitLossBatchDetailExt> enrich(@NotEmpty List<ProfitLossBatchDetailExt> var1);
- }
复制代码 实现类定义在服务提供方的jar中,如下:这个实现类会依赖主数据的jsf服务SkuQueryService,SkuInfoQueryService对SkuQueryService进行rpc封装调用。通过Autowired的方式注入进来,消费者需要定义在xml文件中,这个跟我们通常引入jsf消费者是一样的。示例如下:jx/spring-jsf-consumer.xml- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:jsf="http://jsf.jd.com/schema/jsf"
- xsi:schemaLocation="http://www.springframework.org/schema/beans
- http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
- http://jsf.jd.com/schema/jsf
- http://jsf.jd.com/schema/jsf/jsf.xsd"
- default-lazy-init="false" default-autowire="byName">
- <jsf:consumer id="skuQueryService" interface="com.jdwl.wms.masterdata.api.sku.SkuQueryService"
- alias="${jsf.consumer.masterdata.alias}" protocol="jsf" check="false" timeout="10000" retries="3"/>
- </beans>
复制代码 jar包的使用方可以直接加载consumer资源文件,也可以依赖得服务直接手动加到工程目录下。第一种方式更加方便,但是容易引起冲突,第二种方式虽然麻烦,但能够避免冲突。
2.2.2 扩展点的测试
因为扩展点依赖杰夫的关系,所以需要在配置文件中添加注册中心的配置和依赖服务的相关配置。示例如下:application-config.properties- jsf.consumer.masterdata.alias=wms6-test
- jsf.registry.index=i.jsf.jd.com
复制代码 通过在单元测试中加载consumer资源文件和配置文件把相关的依赖都加载进来,就能够实现对接口的贯穿调用测试。如下代码所示:2.3 jsf接口方式
jsf方式的扩展点实现和jar包方式是一样的,区别是这种方式不需要依赖服务提供方实现的jar,无需加载具体的实现类。通过配置jsf接口的杰夫别名来识别扩展点并进行扩展点的调用。
3 SPI原理分析
3.1dddplus
dddplus-runtime包中ExtensionDef主要是用来加载扩展点bean到InternalIndexer:3.2 java spi
通过上面简单的demo,可以看到最关键的实现就是ServiceLoader这个类,可以看下这个类的源码,如下:上面的代码只贴出了部分关键的实现,有兴趣的读者可以自己去研究,下面贴出比较直观的spi加载的主要流程供参考:

4 总结
SPI的两种提供方式各有优缺点,jar包方式部署成本低、依赖多,增加调用方的配置成本;jsf接口方式部署成本高,但调用方依赖少,只需要通过别名识别不同的BP。
总结下spi能带来的好处:
- 不需要改动源码就可以实现扩展,解耦。
- 实现扩展对原来的代码几乎没有侵入性。
- 只需要添加配置就可以实现扩展,符合开闭原则。
作者:京东物流 贾永强
来源:京东云开发者社区 自猿其说Tech 转载请注明来源
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |