深入理解Java中的spi機制 全名為 是JDK內置的一種服務提供發現機制,是Java提供的一套用來被第三方實現或者擴展的API,它可以用來啟用框架擴展和替換組件。 = 基於介面的編程+策略模式+配置文件 的動態載入機制 Java SPI的具體約定如下: 當服務的提供者,提供了服務介面的一種實現之後 ...
深入理解Java中的spi機制
SPI
全名為Service Provider Interface
是JDK內置的一種服務提供發現機制,是Java提供的一套用來被第三方實現或者擴展的API,它可以用來啟用框架擴展和替換組件。
JAVA SPI
= 基於介面的編程+策略模式+配置文件 的動態載入機制
Java SPI的具體約定如下:
當服務的提供者,提供了服務介面的一種實現之後,在jar
包的META-INF/services/
目錄里同時創建一個以服務介面命名的文件。該文件里就是實現該服務介面的具體實現類。
而當外部程式裝配這個模塊的時候,就能通過該jar
包META-INF/services/
里的配置文件找到具體的實現類名,並裝載實例化,完成模塊的註入。
根據SPI的規範我們的服務實現類必須有一個無參構造方法
。
為什麼一定要在classes
中的META-INF/services
下呢?
JDK提供服務實現查找的一個工具類:java.util.ServiceLoader
在這個類裡面已經寫死
// 預設會去這裡尋找相關信息
private static final String PREFIX = "META-INF/services/";
常見的使用場景:
JDBC
載入不同類型的資料庫驅動- 日誌門面介面實現類載入,
SLF4J
載入不同提供商的日誌實現類 Spring
中大量使用了SPI
,- 對
servlet3.0
規範 - 對
ServletContainerInitializer
的實現 - 自動類型轉換
Type Conversion SPI(Converter SPI、Formatter SPI)
等
- 對
Dubbo
裡面有很多個組件,每個組件在框架中都是以介面的形成抽象出來!具體的實現又分很多種,在程式執行時根據用戶的配置來按需取介面的實現
簡單的spi實例
整體包結構如下
└─main
├─java
│ └─com
│ └─xinchen
│ └─spi
│ └─App.java
│ └─IService.java
│ └─ServiceImplA.java
│ └─ServiceImplB.java
└─resources
└─META-INF
└─services
└─com.xinchen.spi.IService
SPI介面
public interface IService {
void say(String word);
}
具體實現類
public class ServiceImplA implements IService {
@Override
public void say(String word) {
System.out.println(this.getClass().toString() + " say: " + word);
}
}
public class ServiceImplB implements IService {
@Override
public void say(String word) {
System.out.println(this.getClass().toString() + " say: " + word);
}
}
/resource/META-INF/services/com.xinchen.spi.IService
com.xinchen.spi.ServiceImplA
com.xinchen.spi.ServiceImplB
Client類
public class App {
static ServiceLoader<IService> services = ServiceLoader.load(IService.class);
public static void main(String[] args) {
for (IService service:services){
service.say("Hello World!");
}
}
}
// 結果:
// class com.xinchen.spi.ServiceImplA say: Hello World!
// class com.xinchen.spi.ServiceImplB say: Hello World!
源碼解析
java.util.ServiceLoader
中的Fied區域
// 載入具體實現類信息的首碼
private static final String PREFIX = "META-INF/services/";
// 需要載入的介面
// The class or interface representing the service being loaded
private final Class<S> service;
// 用於載入的類載入器
// The class loader used to locate, load, and instantiate providers
private final ClassLoader loader;
// 創建ServiceLoader時採用的訪問控制上下文
// The access control context taken when the ServiceLoader is created
private final AccessControlContext acc;
// 用於緩存已經載入的介面實現類,其中key為實現類的完整類名
// Cached providers, in instantiation order
private LinkedHashMap<String,S> providers = new LinkedHashMap<>();
// 用於延遲載入介面的實現類
// The current lazy-lookup iterator
private LazyIterator lookupIterator;
從ServiceLoader.load(IService.class)
進入源碼中
public static <S> ServiceLoader<S> load(Class<S> service) {
// 獲取當前線程上下文的類載入器
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
在ServiceLoader.load(service, cl)
中
public static <S> ServiceLoader<S> load(Class<S> service,ClassLoader loader){
// 返回ServiceLoader的實例
return new ServiceLoader<>(service, loader);
}
private ServiceLoader(Class<S> svc, ClassLoader cl) {
service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}
public void reload() {
// 清空已經緩存的載入的介面實現類
providers.clear();
// 創建新的延遲載入迭代器
lookupIterator = new LazyIterator(service, loader);
}
private LazyIterator(Class<S> service, ClassLoader loader) {
// 指定this類中的 需要載入的介面service和類載入器loader
this.service = service;
this.loader = loader;
}
當我們通過迭代器獲取對象實例的時候,首先在成員變數providers
中查找是否有緩存的實例對象
如果存在則直接返回,否則則調用lookupIterator
延遲載入迭代器進行載入
迭代器判斷的代碼如下
public Iterator<S> iterator() {
// 返回迭代器
return new Iterator<S>() {
// 查詢緩存中是否存在實例對象
Iterator<Map.Entry<String,S>> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {
// 如果緩存中已經存在返回true
if (knownProviders.hasNext())
return true;
// 如果不存在則使用延遲載入迭代器進行判斷是否存在
return lookupIterator.hasNext();
}
public S next() {
// 如果緩存中存在則直接返回
if (knownProviders.hasNext())
return knownProviders.next().getValue();
// 調用延遲載入迭代器進行返回
return lookupIterator.next();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
LazyIterator的類載入
// 判斷是否擁有下一個實例
private boolean hasNextService() {
// 如果擁有直接返回true
if (nextName != null) {
return true;
}
// 具體實現類的全名 ,Enumeration<URL> config
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;
}
// 轉換config中的元素,或者具體實現類的真實包結構
pending = parse(service, configs.nextElement());
}
// 具體實現類的包結構名
nextName = pending.next();
return true;
}
private S nextService() {
if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class<?> c = null;
try {
// 載入類對象
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {
fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {
fail(service,
"Provider " + cn + " not a subtype");
}
try {
// 通過c.newInstance()實例化
S p = service.cast(c.newInstance());
// 將實現類加入緩存
providers.put(cn, p);
return p;
} catch (Throwable x) {
fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}
總結
優點
使用Java SPI機制的優勢是實現解耦,使得第三方服務模塊的裝配控制的邏輯與調用者的業務代碼分離,而不是耦合在一起。應用程式可以根據實際業務情況啟用框架擴展或替換框架組件。
缺點
多個併發多線程使用ServiceLoader類的實例是不安全的
雖然ServiceLoader也算是使用的延遲載入,但是基本只能通過遍歷全部獲取,也就是介面的實現類全部載入並實例化一遍。