01. Java SPI機制
最近在一個日志標准化的項目中,使用了責任鏈模式來鏈接每一個具體的處理Handler.但是在實例化時,需要每一個都去創建實例。
如:
/**
* 初始化具體的處理類
*/
private void initConcreteHandler() {
handlers.add(new BasicParamHandler());
handlers.add(new CommonParamHandler());
handlers.add(new TestParamHandler());
……
}
這種,涉及了具體的實現類,就違反了可拔插的原則,如果需要替換一種實現,就需要修改代碼。老大看到我的代碼,直接讓用Java spi機制去做。
1. SPI機制簡介
Service Provider Interface:服務提供者接口.例如,系統里抽象的各個模塊,往往有很多不同的實現方案,比如日志模塊的方案,xml解析模塊、jdbc模塊的方案等。
面向的對象的設計里,我們一般推薦模塊之間基於接口編程,模塊之間不對實現類進行硬編碼。為了實現在模塊裝配的時候能不在程序里動態指明,這就需要一種服務發現機制。在模塊化設計中這個機制尤其重要。
java spi就是提供這樣的一個機制:為某個接口尋找服務實現的機制。
2. 實現案例
1.common-logging
apache最早提供的日志的門面接口。只有接口,沒有實現。具體方案由各提供商實現, 發現日志提供商是通過掃描 META-INF/services/org.apache.commons.logging.LogFactory配置文件,通過讀取該文件的內容找到日志提工商實現類。只要我們的日志實現里包含了這個文件,並在文件里制定 LogFactory工廠接口的實現類即可。
2. JDBC
jdbc4.0以前, 開發人員還需要基於Class.forName("xxx")的方式來裝載驅動。
創建連接:
DriverManage.getConnection()中,有Connection con = aDriver.driver.connect(url, info);
driver成員變量,是java.sql.Driver接口,Java對外公開的一個加載驅動接口,Java並未實現,至於實現這個接口由各個Jdbc廠商去實現。
如MySQL,mysql-connector-java-5.1.38.jar包下面META-INF.services包下有個java.sql.Driver文件打開文件有下面兩行
com.mysql.jdbc.Driver
com.mysql.fabric.jdbc.FabricMySQLDriver
3. 具體實現
(1)如,我們這里的處理模塊。面向接口編程。這是我的一種實現方式。
public interface LogProcessHandler {
/**
* 解析日志,具體的給Bean賦值的邏輯
* @param context 上下文
*/
void process(ProcessorContext context);
/**
* 日志解析handler拓撲順序,從小到大排列
*/
int order();
}
(2)每一個具體實現類,如BasicParamHandler、CommonParamHandler都實現了這個接口,里面具體的process()方法。
(3)在Maven工程的src/main/resources/下,創建META-INF/services/com.A.standard.chain.LogProcessHandler文件(UTF-8)
里面具體內容:
com.A.tools.BasicParamProcessHandler
com.A.tools.CommonParamProcessHandler
com.A.tools.TestParamProcessHandler
(4)主類中初始化
/**
* 初始化具體的處理類
*/
private void initConcreteHandler() {
// 初始化
handlers = new ArrayList<>();
// 通過java spi機制load所有處理的hander
ServiceLoader<LogProcessHandler> loader = ServiceLoader.load(LogProcessHandler.class);
for (LogProcessHandler hander : loader ) {
handlers.add(hander);
}
// handler 排序
Collections.sort(handlers, new Comparator<LogProcessHandler>() {
@Override
public int compare(LogProcessHandler o1, LogProcessHandler o2) {
return o1.order() - o2.order();
}
});
}
Ps:
一般情況下,是按照文件中實現類順序加載類,但是可能出現特殊情況。因此對集合中的實現類進行排序,這也是接口中定義了order方法的原因。
4. 原理
當服務的提供者,提供了服務接口的一種實現之后,在jar包的META-INF/services/目錄里同時創建一個以服務接口命名的文件。該文件里就是實現該服務接口的具體實現類。
當外部程序裝配這個模塊的時候,就能通過該jar包META-INF/services/里的配置文件找到具體的實現類名,並裝載實例化,完成模塊的注入。
基於這樣一個約定就能很好的找到服務接口的實現類,而不需要再代碼里制定。