SPI
全名爲Service Provider Interface
是JDK內置的一種服務提供發現機制,是Java提供的一套用來被第三方實現或者擴展的API,它能夠用來啓用框架擴展和替換組件。html
JAVA SPI
= 基於接口的編程+策略模式+配置文件 的動態加載機制java
Java SPI的具體約定以下:git
當服務的提供者,提供了服務接口的一種實現以後,在jar
包的META-INF/services/
目錄裏同時建立一個以服務接口命名的文件。該文件裏就是實現該服務接口的具體實現類。github
而當外部程序裝配這個模塊的時候,就能經過該jar
包META-INF/services/
裏的配置文件找到具體的實現類名,並裝載實例化,完成模塊的注入。數據庫
根據SPI的規範咱們的服務實現類必須有一個無參構造方法
。編程
爲何必定要在classes
中的META-INF/services
下呢?緩存
JDK提供服務實現查找的一個工具類:java.util.ServiceLoader
安全
在這個類裏面已經寫死bash
// 默認會去這裏尋找相關信息 private static final String PREFIX = "META-INF/services/";
常見的使用場景:多線程
JDBC
加載不一樣類型的數據庫驅動SLF4J
加載不一樣提供商的日誌實現類Spring
中大量使用了SPI
,
servlet3.0
規範ServletContainerInitializer
的實現Type Conversion SPI(Converter SPI、Formatter SPI)
等Dubbo
裏面有不少個組件,每一個組件在框架中都是以接口的造成抽象出來!具體的實現又分不少種,在程序執行時根據用戶的配置來按需取接口的實現總體包結構以下
└─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也算是使用的延遲加載,可是基本只能經過遍歷所有獲取,也就是接口的實現類所有加載並實例化一遍。