总所周知,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
新建一个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
调用的module引用
org.example service-impl-one1.0-SNAPSHOT org.example service-imp-two1.0-SNAPSHOT
通过ServiceLoader获取所有实现类并排序后依次调用
ServiceLoaderloader = ServiceLoader.load(MyServiceSpi.class); Iterator iterator = loader.iterator(); List myServiceSpiList = new ArrayList<>(); while(iterator.hasNext()){ myServiceSpiList.add(iterator.next()); } // 根据order进行排序,order值小的先执行 Collections.sort(myServiceSpiList, new Comparator () { @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
调用扩展接口地方是不需要关心接口到底有没有实现类,有多少个实现类,这达到我们想要的模块之间解耦。但是,发现没有,提供接口的jar要和实现的jar跑在同一个jvm里面,这就意味着要么提前把扩展jar包全部打到我们的应用管理服务中,要么支持在单独安装或者升级时动态加载扩展jar,然后reload重新加载实现类。



