基于动态代理的SPI框架
SPI的全名为Service Provider Interface。根据Java的SPI规范,我们可以定义一个服务接口,具体的实现由对应的实现者去提供,即Service Provider(服务提供者)。然后在使用的时候只要根据SPI的规范去获取对应的服务提供者的服务实现即可。简单来说,SPI就是一种为接口寻找服务的机制,基于这种机制,本文提出一种基于动态代理的spi框架的实现方式。
1 基于统一的接口
该SPI框架的核心思想是通过JDK动态代理(代理对象必须为某个接口的实现),为接口寻找满足条件的服务实现。基于上述要求,需要提供一个统一的接口。
public interface SpiBase<T, R> {
/**
* 是否执行当前实现的条件
* @param query
* @return
*/
boolean condition(T query);
/**
* 具体操作
* @param query
* @return
*/
List<R> invoke(T query);
/**
* spi执行时候的配置
* @param query
* @return
*/
SpiConfig config(T query);
}
作为框架,需要尽可能的符合各类场景
- 提供condition方法以及模板类T参数,让用户自定义符合接口实现的要求
- 提供统一的invoke方法来统一服务的某一类行为
- 提供spi执行的config,用来扩展寻求实现的策略
public class SpiConfig {
/**
* 是否互斥
* false:满足执行条件的其他实现也一并调用
* true:当前实现满足执行要求并执行成功后直接返回,忽略满足执行条件的其他实现
*/
private boolean mutex;
/**
* 优先值
* 该值越高,执行优先级越高
*/
private int priority;
/**
* 名称 作为标识
*/
private String name;
....
}
- 参数mutex,决定了某个接口,当有多个满足条件的服务实现时的执行策略,是只选择优先级最高的服务实现去执行,还是说按照优先级顺序多个服务实现顺序执行后将结果合并返回(这也是为什么invoke方法返回list的原因)。
- 参数priority,决定某个服务实现的执行优先级,优先级越高,越先执行
2 获取所有用户自定义的服务实现并实例化
2.1 提供注解@BizSpi
@Target({ElementType.TYPE})//目标作用于类
@Retention(RetentionPolicy.RUNTIME)// 注解在class字节码文件中存在,在运行时可以通过反射获取到
@Documented
@Service
public @interface BizSpi {
}
为了方便的找到用户提供的服务实现类,框架约束用户在其提供的服务实现类上加上注解@BizSpi,当然所有的服务实现类都需要实现统一的接口SpiBase,也可以实现某个继承SpiBase接口的自定义接口。
2.2 实例化服务实现并保存至内存
public class SpiManager implements ApplicationListener<ContextRefreshedEvent> {
private static Map<Class, List<SpiBase>> spiProviderMap = new ConcurrentHashMap<Class, List<SpiBase>>();
private static volatile boolean isLoaded = false;
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
if (!isLoaded) {
Map<String, Object> spiInstanceMap = contextRefreshedEvent.getApplicationContext().getBeansWithAnnotation(BizSpi.class);
for (Object spiInstance : spiInstanceMap.values()) {
Class spiInterface = spiInstance.getClass().getInterfaces()[0];
if (spiProviderMap.containsKey(spiInterface) && spiProviderMap.get(spiInterface).size() > 0) {
spiProviderMap.get(spiInterface).add((SpiBase) spiInstance);
} else {
List<SpiBase> spiProviderList = new ArrayList<SpiBase>();
spiProviderList.add((SpiBase) spiInstance);
spiProviderMap.put(spiInterface, spiProviderList);
}
}
for (List<SpiBase> spiProviderList : spiProviderMap.values()) {
Collections.sort(spiProviderList, new Comparator<SpiBase>() {
public int compare(SpiBase spiBase1, SpiBase spiBase2) {
return spiBase2.config(null).getPriority() - spiBase1.config(null).getPriority();
}
});
}
}
isLoaded = true;
}
public static List<SpiBase> getSpiProviderMap(Class spiInterface) {
return spiProviderMap.get(spiInterface);
}
}
这里通过约束的注解@BizSpi来拿到所有服务实现实例化后的对象,通过spiProviderMap来保存管理,其中spiProviderMap的key的集合是所有服务实现所实现的第一级接口(可以是SpiBase,也可以是继承自SpiBase的自定义接口),value则是key代表的接口所对应的所有服务实现实例化后的对象列表(可以看到列表通过SpiConfig中的priority值进行了重排序)。
3 动态代理
public class SpiProviderFactory<T> implements FactoryBean<T> {
private Logger logger = LoggerFactory.getLogger(SpiProviderFactory.class);
private Class<T> targetClass;
public T getObject() throws Exception {
InvocationHandler invocationHandler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
List<SpiBase> spiList = SpiManager.getSpiProviderMap(targetClass);
// 支持组合,所以必须返回list; 每一个功能点实现也必须返回list
List combinationResult = new ArrayList();
for (SpiBase spi : spiList) {
if (SpiBase.class.isInstance(spi)) {
if (args != null && spi.condition(args[0])) {
SpiConfig spiConfig = spi.config(args[0]);
List result = (List) method.invoke(spi, args);
if (result != null) {
combinationResult.addAll(result);
if (spiConfig.isMutex()) {
break;
}
}
}
}
}
return combinationResult;
}
};
return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{targetClass},
invocationHandler);
}
...
}
这里用到了工厂,这里需要传入参数targetClass,因为我们需要代理的接口可能是继承了SpiBase的自定义接口。通过服务实现类提供的condition方法可以从spiProviderMap中找到满足条件的服务,在通过SpiConfig的中参数决定服务执行的策略,最终将结果返回。
4 使用步骤
1.继承SpiBase得到自定义的SPI接口(可以添加自定义方法) 2.为SPI接口提供多个服务实现类(注意类需要加上注解@BizSpi),condition方法决定了服务是否满足执行条件,config方法返回的SpiConfig对象决定了服务执行的策略 3.spring配置
<!--扫描spi框架-->
<context:component-scan base-package="org.cuner.spi.framework"/>
<!--还需要扫描到你自己的服务实现类-->
<bean id="demoSpi" class="org.cuner.spi.framework.core.SpiProviderFactory">
<property name="targetClass" value="org.cuner.spi.framework.demo.api.demoSpi"/>
</bean>
4.spring注入后使用
@Resource(name = "demoSpi")
private DemoSpi demoSpi;
5.demo 代码托管在Github上,并附有使用demo,欢迎下载运行: https://github.com/Cuner/spi-framework