一、什麼是 SPI
SPI 全名 Service Provider interface,翻譯過來就是“服務提供介面”。基本效果是,申明一個介面,然後透過配置獲取它的實現,進而實現動態擴充套件。
Java SPI 是 JDK 內建的一種動態載入擴充套件點的實現。
一般的業務程式碼中較少用到,但是在底層框架中卻大量使用,包括 JDBC、Dubbo、Spring、Solon、slf4j 等框架都有用到,不同的是有的使用 Java 原生的實現,有的框架則自己實現了一套 SPI 機制.
二、Spring SPI
Spring 中的 SPI 相比於 JDK 原生的,它的功能更強大些,它可以替換的型別不僅僅侷限於介面/抽象類,它可以是任何一個類,介面,註解;
正因為 Spring SPI 是支援替換註解型別的 SPI,這個特性在 Spring Boot 中的自動裝配有體現(EnableAutoConfiguration註解):
Spring 的 SPI 配置檔案,需要放在工程的 META-INF 下,且檔名為 spring.factories ,而檔案的內容本質就是一個 properties;如 spring-boot-autoconfigure 包下的 META-INF/spring.factories 檔案,用於自動裝配的。
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration, \
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration, \
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration, \
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration, \
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration, \
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,
三、Solon SPI
相對於 Java SPI 和 Spring SPI 的“配置式”風格。Solon SPI 則是 “編碼式” 風格。就有點兒像 Maven 和 Gradle。Solon SPI,也稱為 Solon Plugin SPI。 同樣需要一個配置檔案,來申明 Plugin 的實現類。
約定外掛配置檔案,且要求檔名是唯一的:
#建議使用包做為檔名,便於識別,且可避免衝突
META-INF/solon/{packname}.properties
約定外掛配置內容(就固定的兩項):
#外掛實現類配置
solon.plugin={PluginImpl}
#外掛最佳化級配置。越大越優先,預設為0
solon.plugin.priority=1
外掛程式碼示例(相當於,為整個 “模組” 提供了一個生命週期)。把上面 Spring SPI 的配置翻譯過來就是:
public class SpringTranslatePlugin implements Plugin{
@Override
public void start(AppContext context) {
//外掛啟動時...
context.beanMake(SpringApplicationAdminJmxAutoConfiguration.class);
context.beanMake(AopAutoConfiguration.class);
context.beanMake(RabbitAutoConfiguration.class);
context.beanMake(BatchAutoConfiguration.class);
context.beanMake(CacheAutoConfiguration.class);
context.beanMake(CassandraAutoConfiguration.class);
}
@Override
public void prestop() throws Throwable {
//外掛預停止時(啟用安全停止時:預停止後隔幾秒才會進行停止)
}
@Override
public void stop(){
//外掛停止時
}
}
因為是 “編碼式” 的。所以也可以做更復雜的控制處理。比如:
public class SolonDataPlugin implements Plugin {
@Override
public void start(AppContext context) {
//註冊快取工廠
CacheLib.cacheFactoryAdd("local", new LocalCacheFactoryImpl());
//新增事務控制支援
if (context.app().enableTransaction()) {
context.beanInterceptorAdd(Tran.class, TranInterceptor.instance, 120);
}
//新增快取控制支援
if (context.app().enableCaching()) {
CacheLib.cacheServiceAddIfAbsent("", LocalCacheService.instance);
context.subWrapsOfType(CacheService.class, new CacheServiceWrapConsumer());
context.lifecycle(() -> {
if (context.hasWrap(CacheService.class) == false) {
context.wrapAndPut(CacheService.class, LocalCacheService.instance);
}
});
context.beanInterceptorAdd(CachePut.class, new CachePutInterceptor(), 110);
context.beanInterceptorAdd(CacheRemove.class, new CacheRemoveInterceptor(), 110);
context.beanInterceptorAdd(Cache.class, new CacheInterceptor(), 111);
}
//自動構建資料來源
Props props = context.cfg().getProp("solon.dataSources");
if (props.size() > 0) {
context.app().onEvent(AppPluginLoadEndEvent.class, e -> {
//支援 ENC() 加密符
VaultUtils.guard(props);
buildDataSource(context, props);
});
}
}
}