SPI都不知道?還敢說懂Dubbo?面試官懟得我啞口無言啊

為什麼要講SPI呢?因為在Dubbo就用到了SPI機制,所以掌握了這部分對於後面的學習還是很有幫助的。

SPI ,全稱為 Service Provider Interface,是一種服務發現機制。它透過在ClassPath路徑下的META-INF/services資料夾查詢檔案,自動載入檔案裡所定義的類。這一機制為很多框架擴充套件提供了可能,比如在Dubbo、JDBC中都使用到了SPI機制。我們先透過一個很簡單的例子來看下它是怎麼用的。

案例介紹

先定義介面專案

SPI都不知道?還敢說懂Dubbo?面試官懟得我啞口無言啊

然後建立一個擴充套件的實現,先匯入上面介面專案的依賴

com。bobo JavaSPIBase 1。0-SNAPSHOT

然後建立介面的實現

/** * SPI:MySQL對於 baseURL 的一種實現 */public class MySQLData implements BaseData { @Override public void baseURL() { System。out。println(“mysql 的擴充套件實現。。。。”); }}

然後在resources目錄下建立 META-INF/services 目錄,然後在目錄中建立一個檔案,名稱必須是定義的介面的全類路徑名稱。然後在檔案中寫上介面的實現類的全類路徑名稱。

SPI都不知道?還敢說懂Dubbo?面試官懟得我啞口無言啊

同樣的再建立一個案例

然後在測試的專案中測試

public static void main(String[] args) { ServiceLoader providers = ServiceLoader。load(BaseData。class); Iterator iterator = providers。iterator(); while(iterator。hasNext()){ BaseData next = iterator。next(); next。baseURL(); } }

根據不同的匯入,執行的邏輯會有不同

SPI都不知道?還敢說懂Dubbo?面試官懟得我啞口無言啊

SPI都不知道?還敢說懂Dubbo?面試官懟得我啞口無言啊

原始碼檢視

ServiceLoader

首先來看下ServiceLoader的類結構

// 配置檔案的路徑 private static final String PREFIX = “META-INF/services/”; // 載入的服務 類或者介面 private final Class service; // 類載入器 private final ClassLoader loader; // 訪問許可權的上下文物件 private final AccessControlContext acc; // 儲存已經載入的服務類 private LinkedHashMap providers = new LinkedHashMap<>(); // 內部類,真正載入服務類 private LazyIterator lookupIterator;

load

load方法建立了一些屬性,重要的是例項化了內部類,LazyIterator。

public final class ServiceLoader implements Iterable private ServiceLoader(Class 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(); //例項化內部類 LazyIterator lookupIterator = new LazyIterator(service, loader); }}

查詢實現類和建立實現類的過程,都在LazyIterator完成。當我們呼叫iterator。hasNext和iterator。next方法的時候,實際上呼叫的都是LazyIterator的相應方法。

private class LazyIterator implements Iterator{ Class service; ClassLoader loader; Enumeration configs = null; Iterator pending = null; String nextName = null; private boolean hasNextService() { //第二次呼叫的時候,已經解析完成了,直接返回 if (nextName != null) { return true; } if (configs == null) { //META-INF/services/ 加上介面的全限定類名,就是檔案服務類的檔案 //META-INF/services/com。viewscenes。netsupervisor。spi。SPIService String fullName = PREFIX + service。getName(); //將檔案路徑轉成URL物件 configs = loader。getResources(fullName); } while ((pending == null) || !pending。hasNext()) { //解析URL檔案物件,讀取內容,最後返回 pending = parse(service, configs。nextElement()); } //拿到第一個實現類的類名 nextName = pending。next(); return true; }}

建立例項物件,當然,呼叫next方法的時候,實際呼叫到的是,lookupIterator。nextService。它透過反射的方式,建立實現類的例項並返回。

private class LazyIterator implements Iterator{ private S nextService() { //全限定類名 String cn = nextName; nextName = null; //建立類的Class物件 Class<?> c = Class。forName(cn, false, loader); //透過newInstance例項化 S p = service。cast(c。newInstance()); //放入集合,返回例項 providers。put(cn, p); return p; }}

看到這兒,我想已經很清楚了。獲取到類的例項,我們自然就可以對它為所欲為了!

影片介紹:

SPI都不知道?還敢說懂Dubbo?面試官懟得我啞口無言啊