使用jdk的spi机制实现接口的扩展和解耦
0、背景总所周知,SPI在很多地方都有着很好的实践,比如JDBC驱动的加载、dubbo等,SpringBoot项目的autoConfiguration也是类似的原理。这里就感觉用来做模块解耦也不错。比如我们做应用管理的一个服务,它具有应用安装、升级、扩容等功能,经常在安装、升级、扩容流程中应用要求做一些定制化的东西,比如安装数据库,需要初始化database;安装kafka,需要初始化topic;
0、背景
总所周知,SPI在很多地方都有着很好的实践,比如JDBC驱动的加载、dubbo等,SpringBoot项目的autoConfiguration也是类似的原理。这里就感觉用来做模块解耦也不错。比如我们做应用管理的一个服务,它具有应用安装、升级、扩容等功能,经常在安装、升级、扩容流程中应用要求做一些定制化的东西,比如安装数据库,需要初始化database;安装kafka,需要初始化topic;对kafka的消费者进行扩容,需要同步扩topic的分区等等。而对于做应用管理的服务来说,它不应该区别化的对待这些应用,否则随着时间的发展,这个服务就会变得越来越复杂,难以与上层应用解耦。
这时候我们可以使用SPI的机制,为每一个支持扩展的流程定义一个扩展接口,交给上层应用去实现,使用SPI来加载这些实现类,从而实现让应用在安装、升级、扩容流程中加入自己个性化的功能。
对于应用管理服务和应用自身都有好处:
- 于应用管理服务而言,只需要维护扩展接口,无需理解应用的逻辑,无需维护应用的个性化定制功能
- 于上层应用而言,无需理解应用管理服务的逻辑,需要扩展就自己去实现接口,自己维护定制化的流程代码,当不需要的定制时候直接删除实现即可。
1、先玩一玩SPI吧
1.1 定义一个接口
新建一个module,名为service-spi,新增如下的接口:
package com.example.service.spi;
public interface MyServiceSpi {
int order();
void service();
}
这个接口中有两个方法,service是让应用自己去实现的扩展,order用来控制多个实现类的顺序,万一多个应用之间有顺序要求呢,可用这个order来控制。
1.2 定义第一个实现类
新建一个module名为service-impl-one,pom.xml中引用service-spi,新增实现类:
package com.example.service.imp.one;
import com.example.service.spi.MyServiceSpi;
public class ServiceImpleOne implements MyServiceSpi {
public int order() {
return 0;
}
public void service() {
System.out.println("my service one");
}
}
在resources中增加文件夹META-INF/services,新建一个名为com.example.service.spi.MyServiceSpi的文件,与接口的全路径一致
文件内容为com.example.service.imp.one.ServiceImpleOne
1.3 定义第二个实现类
新建一个module名为service-impl-two,pom.xml中引用service-spi,新增实现类:
package com.example.service.imp.two;
import com.example.service.spi.MyServiceSpi;
public class ServiceImplTwo implements MyServiceSpi {
public int order() {
return 1;
}
public void service() {
System.out.println("my service two");
}
}
在resources中增加文件夹META-INF/services,新建一个名为com.example.service.spi.MyServiceSpi的文件,与接口的全路径一致
文件内容为com.example.service.imp.two.ServiceImplTwo
1.4 调用MyServiceSpi
调用的module引用
<dependency>
<groupId>org.example</groupId>
<artifactId>service-impl-one</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.example</groupId>
<artifactId>service-imp-two</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
通过ServiceLoader获取所有实现类并排序后依次调用
ServiceLoader<MyServiceSpi> loader = ServiceLoader.load(MyServiceSpi.class);
Iterator<MyServiceSpi> iterator = loader.iterator();
List<MyServiceSpi> myServiceSpiList = new ArrayList<>();
while(iterator.hasNext()){
myServiceSpiList.add(iterator.next());
}
// 根据order进行排序,order值小的先执行
Collections.sort(myServiceSpiList, new Comparator<MyServiceSpi>() {
@Override
public int compare(MyServiceSpi o1, MyServiceSpi o2) {
return o1.order()- o2.order();
}
});
// 串行调用所有实现类
for (MyServiceSpi serviceSpi:myServiceSpiList) {
serviceSpi.service();
}
运行后结果
my service one
my service two
2、思考
- 调用扩展接口地方是不需要关心接口到底有没有实现类,有多少个实现类,这达到我们想要的模块之间解耦。
- 但是,发现没有,提供接口的jar要和实现的jar跑在同一个jvm里面,这就意味着要么提前把扩展jar包全部打到我们的应用管理服务中,要么支持在单独安装或者升级时动态加载扩展jar,然后reload重新加载实现类。
更多推荐
所有评论(0)