Skip to content

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会自动管理其加载实例的生命周期,并且提供了InitialingBeanDisposableBean分别用于目标实例后初始化和目标实例销毁前回调。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();

贡献者

页面历史

Released under the MIT License.