什麼是SPI
軟件很大程度是真實世界的反映,思想、模式都能在真實事物找到影子。這不難理解,因爲不管是真實事物還是軟件都是由人類創造,都是朝着高效、低成本方向發展,難免相互借鑑。SPI也是如此,SPI全稱Service Provider Interface。本質就是上層軟件制定所需模塊的接口規範,然後基於這個規範搭建上層建築,至於模塊誰來開發實現並不關心,反正廠商按照接口規範做出來就好。好比汽車廠商開發一款新型汽車,輪胎不要他生產,他只要告訴輪胎廠商這款車要用195/55/R16 85V 規格的輪胎,輪胎廠商按照這個標準生產即可。
SPI的應用比較常見,比如說 (1) jdbc驅動,如果針對具體的數據庫開發軟件,換數據庫將是災難級調整。所以業務面向jdbc接口規範開發,利用SPI自動發現接口實現類將極大提高軟件的擴展性;(2) 之前寫過的ServletContainerInitializer,servlet容器用SPI發現spring-web包中的SpringServletContainerInitializer從而啓動初始化應用;(3) 此外,還有日誌框架、validation(hibernator-validator)也是基於SPI機制發現具體的服務實現。
SPI機制原理
SPI的核心是java.util.ServiceLoader。ServiceLoader大致的過程是在classpath*:META-INF/services/目錄下查找配置文件(名稱是接口的全路徑類名),這個文件裏面每一行就是實現類的全路徑類名,然後用當前線程的classloader加載實現類。具體實現可以看看jdk源碼:
public final class ServiceLoader<S> implements Iterable<S>{
//查找的目錄
private static final String PREFIX = "META-INF/services/";
......
//內部類 具體的查找的實現
private class LazyIterator implements Iterator<S>{
......
private boolean hasNextService() {
if (nextName != null) {
return true;
}
if (configs == null) {
try {
String fullName = PREFIX + service.getName();
if (loader == null)
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {
fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {
if (!configs.hasMoreElements()) {
return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}
......
}
......
}
spi demo程序
整一個demo程序跑一跑,否則太乾巴巴的了,demo程序儘量簡單,只是打個日誌,實現2個具體的實現類。
首先,定一個接口LogPrint
public interface LogPrint {
//打印日誌
void print(String message);
}
其次,創建2個實現類LogPrintImplA、LogPrintImplB
package com.focuse.jdkdemo.spi;
public class LogPrintImplA implements LogPrint {
@Override
public void print(String message) {
System.out.println("LogPrinterA: " + message);
}
}
package com.focuse.jdkdemo.spi;
public class LogPrintImplB implements LogPrint {
@Override
public void print(String message) {
System.out.println("LogPrinterB: " + message);
}
}
第三步,在目錄META-INF/services下創建文件com.focuse.jdkdemo.spi.LogPrint,文件內容2行。
最後,用ServiceLoader加載實現類,並運行程序
package com.focuse.jdkdemo.spi;
import java.util.Iterator;
import java.util.ServiceLoader;
/**
* @author :focuse
* @date :Created in 2020/2/16 上午11:10
* @description:
* @modified By:
*/
public class SPIDemo {
public static void main(String[] args) {
ServiceLoader<LogPrint> loader = ServiceLoader.load(LogPrint.class);
Iterator<LogPrint> it = loader.iterator();
while (it.hasNext()) {
it.next().print("hello spi!");
}
}
}
ps: 爲了讓demo程序簡單,就沒有用jar引用的形式,而是直接在一個工程裏面完成所有的事情