SPI 增强
概述
SPI
增强特性提供了SPI
加载、单例和非单例声明、后置处理器、生命周期管理等等强大的功能。
注解声明
@Spi
使用在(接口)类型上,它提供的参数如下:
value()
:用于指定默认实现的key
singleton()
:标记加载的实例是否单例,默认为true
lazy()
:标记需要被加载的单例是否懒加载,默认为true
初始化 SpiLoader
SPI
增强特性主要由SpiLoader
提供,首先要初始化SpiLoader
:
java
// 至少需要指定目标类型
SpiLoader<DemoApi> loader1 = SpiLoader.getSpiLoader(DemoApi.class);
// 定义后置处理器列表
List<SpiPostProcessor> postProcessors = new ArrayList<>();
SpiLoader<DemoApi> loader2 = SpiLoader.getSpiLoader(DemoApi.class, postProcessors);
// 定义加载策略列表、后置处理器列表,并且按配置顺序获取实例
List<LoadingStrategy> loadingStrategies = new ArrayList<>();
SpiLoader<DemoApi> loader3 = SpiLoader.getSpiLoader(DemoApi.class, loadingStrategies, postProcessors, true);
加载策略的接口定义是LoadingStrategy
,用于指定SpiLoader
对于目标类型的子类(实现类)的搜索路径和加载参数等等。内置的LoadingStrategy
实现有:
InternalLoadingStrategy
:加载目录为META-INF/solpic/internal/
DefaultLoadingStrategy
:加载目录为META-INF/solpic/
ServicesLoadingStrategy
:加载目录为META-INF/services/
,和JDK
内置的ServiceLoader
的加载目录是一致的
加载的目标文件需要以接口全类名作为文件名放在对应加载策略的加载目录下,内容按照spiName=implClassName
格式编写。例如:
java
@Spi("demo")
interface DemoApi {
}
class DemoApiImpl implements DemoApi {
}
// 文件名:META-INF/solpic/xx.yy.zz.DemoApi
// 文件内容如下:
demo=xx.yy.zz.DemoApiImpl
如果内置的LoadingStrategy
实现不满足实际需求,可以自定义加载策略:
java
class CustomLoadingStrategy implements LoadingStrategy {
@Override
public String name() {
return "CUSTOM";
}
@Override
public String location() {
return "META-INF/custom/";
}
}
然后初始化SpiLoader
的时候使用此自定义LoadingStrategy
实现即可:
java
List<LoadingStrategy> loadingStrategies = new ArrayList<>();
loadingStrategies.add(new CustomLoadingStrategy());
SpiLoader<DemoApi> loader = SpiLoader.getSpiLoader(DemoApi.class, loadingStrategies, null, true);
生命周期
SpiLoader
会自动管理其加载实例的生命周期,并且提供了InitialingBean
和DisposableBean
分别用于目标实例后初始化和目标实例销毁前回调。DisposableBean
有使用限制,仅对单例生效(也就是@Spi
中的singleton()
必须为true
)。例如:
java
@Spi
interface DemoApi {
}
class DemoApiImpl implements DemoApi, InitialingBean, DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("DemoApiImpl destroy...");
}
@Override
public void init() {
System.out.println("DemoApiImpl init...");
}
}
后置处理器
后置处理器SpiPostProcessor
的回调时机分别是InitialingBean#init()
方法回调的前后,用于目标实例初始化前后的一些扩展操作。举个例子:
java
class CustomSpiPostProcessor implements SpiPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String name) {
System.out.printf("postProcessBeforeInitialization: %s\n", name);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String name) {
System.out.printf("postProcessAfterInitialization: %s\n", name);
return bean;
}
}
List<SpiPostProcessor> postProcessors = new ArrayList<>();
postProcessors.add(new CustomSpiPostProcessor());
SpiLoader<DemoApi> loader = SpiLoader.getSpiLoader(DemoApi.class, postProcessors);
DemoApi demoApi = loader.getService("demo");
输出结果:
java
postProcessBeforeInitialization: demo
DemoApiImpl init...
postProcessAfterInitialization: demo
加载实例
SpiLoader
初始化完成之后,可以加载目标实例。还是使用DemoApi
接口类型的例子:
java
// 初始化SpiLoader
SpiLoader<DemoApi> loader = SpiLoader.getSpiLoader(DemoApi.class);
// 通过key加载对应的实例
DemoApi demoApi = loader.getService("demo");
// 加载默认实现的key
String defaultServiceName = loader.getDefaultServiceName();
// 加载所有目标类型实现对应的key
Set<String> availableServiceNames = loader.getAvailableServiceNames();
// 加载所有目标类型的实现类型
Set<Class<?>> availableServiceTypes = loader.getAvailableServiceTypes();
// 加载所有目标类型的实例
List<DemoApi> availableServices = loader.getAvailableServices();