摘要:簡介全稱為,是一種服務發(fā)現(xiàn)機制。的本質(zhì)是將接口實現(xiàn)類的全限定名配置在文件中,并由服務加載器讀取配置文件,加載實現(xiàn)類。不過,并未使用原生的機制,而是對其進行了增強,使其能夠更好的滿足需求。并未使用,而是重新實現(xiàn)了一套功能更強的機制。
1、SPI簡介
SPI 全稱為 Service Provider Interface,是一種服務發(fā)現(xiàn)機制。SPI 的本質(zhì)是將接口實現(xiàn)類的全限定名配置在文件中,并由服務加載器讀取配置文件,加載實現(xiàn)類。這樣可以在運行時,動態(tài)為接口替換實現(xiàn)類。正因此特性,我們可以很容易的通過 SPI 機制為我們的程序提供拓展功能。SPI 機制在第三方框架中也有所應用,比如 Dubbo 就是通過 SPI 機制加載所有的組件。不過,Dubbo 并未使用 Java 原生的 SPI 機制,而是對其進行了增強,使其能夠更好的滿足需求。在 Dubbo 中,SPI 是一個非常重要的模塊?;?SPI,我們可以很容易的對 Dubbo 進行拓展。如果大家想要學習 Dubbo 的源碼,SPI 機制務必弄懂。接下來,我們先來了解一下 Java SPI 與 Dubbo SPI 的用法,然后再來分析 Dubbo SPI 的源碼。
2、SPI示例 2.1 Java SPI示例本節(jié)通過一個示例演示 Java SPI 的使用方法。首先,我們定義一個接口,名稱為 HelloService。
public interface HelloService { void sayHello(); }
家下來定義兩個實現(xiàn)類:HelloAService、HelloBService;
public class HelloAService implements HelloService { @Override public void sayHello() { System.out.println("Hello, I am A"); } } public class HelloBService implements HelloService { @Override public void sayHello() { System.out.println("Hello, I am B"); } }
接下來 META-INF/services 文件夾下創(chuàng)建一個文件,名稱為 Robot 的全限定名 org.apache.spi.Robot。文件內(nèi)容為實現(xiàn)類的全限定的類名,如下:
org.apache.spi.HelloAService org.apache.spi.HelloBService
做好所需的準備工作,接下來編寫代碼進行測試
public class JavaSPITest { @Test public void sayHello() throws Exception { ServiceLoaderserviceLoader = ServiceLoader.load(HelloService.class); System.out.println("Java SPI"); serviceLoader.forEach(HelloService::sayHello); } }
最后結(jié)果如下:
Java SPI Hello, I am A Hello, I am B
從測試結(jié)果可以看出,我們的兩個實現(xiàn)類被成功的加載,并輸出了相應的內(nèi)容。關于 Java SPI 的演示先到這里,接下來演示 Dubbo SPI。
2.2 Dubbo SPIDubbo 并未使用 Java SPI,而是重新實現(xiàn)了一套功能更強的 SPI 機制。Dubbo SPI 的相關邏輯被封裝在了 ExtensionLoader 類中,通過 ExtensionLoader,我們可以加載指定的實現(xiàn)類。Dubbo SPI 所需的配置文件需放置在 META-INF/dubbo 路徑下,配置內(nèi)容如下。
helloAService = org.apache.spi.HelloAService helloBService = org.apache.spi.HelloBService
與 Java SPI 實現(xiàn)類配置不同,Dubbo SPI 是通過鍵值對的方式進行配置,這樣我們可以按需加載指定的實現(xiàn)類。另外,在測試 Dubbo SPI 時,需要在 Robot 接口上標注 @SPI 注解。下面來演示 Dubbo SPI 的用法:
public class DubboSPITest { @Test public void sayHello() throws Exception { ExtensionLoaderextensionLoader = ExtensionLoader.getExtensionLoader(HelloService.class); HelloService a = extensionLoader.getExtension("HelloAService"); a.sayHello(); HelloService b = extensionLoader.getExtension("HelloBService"); b.sayHello(); } }
測試結(jié)果如下:
Java SPI Hello, I am A Hello, I am B3、Dubbo SPI源碼解析
Dubbo SPI相關邏輯都在ExtensionLoader 類中,首先通過getExtensionLoader獲取一個ExtensionLoader實例,然后在根據(jù)getExtension獲取type的擴展類。
getExtensionLoader方法比較簡單,先從緩存中獲取,如果緩存不存在,則創(chuàng)建ExtensionLoader對象,并存入緩存中,在看getExtension方法:
public T getExtension(String name) { if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("Extension name == null"); } if ("true".equals(name)) { return getDefaultExtension(); } // 根據(jù)擴展名從緩存中獲取,緩存中沒有,創(chuàng)建并存入緩存 Holder
上面代碼的邏輯比較簡單,首先檢查緩存,緩存未命中則創(chuàng)建拓展對象。下面我們來看一下創(chuàng)建拓展對象的過程是怎樣的。
private T createExtension(String name) { // 從配置文件中加載所有的拓展類,可得到“配置項名稱”到“配置類”的映射關系表 Class> clazz = getExtensionClasses().get(name); if (clazz == null) { throw findException(name); } try { T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } injectExtension(instance); Set> wrapperClasses = cachedWrapperClasses; if (CollectionUtils.isNotEmpty(wrapperClasses)) { for (Class> wrapperClass : wrapperClasses) { // 將當前 instance 作為參數(shù)傳給 Wrapper 的構(gòu)造方法,并通過反射創(chuàng)建 Wrapper 實例。 // 然后向 Wrapper 實例中注入依賴,最后將 Wrapper 實例再次賦值給 instance 變量 instance = injectExtension((T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; } catch (Throwable t) { throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type + ") could not be instantiated: " + t.getMessage(), t); } }
我們在通過名稱獲取拓展類之前,首先需要根據(jù)配置文件解析出拓展項名稱到拓展類的映射關系表(Map<名稱, 拓展類>),之后再根據(jù)拓展項名稱從映射關系表中取出相應的拓展類即可。相關過程的代碼分析如下
private Map> getExtensionClasses() { // 從緩存中獲取 Map > classes = cachedClasses.get(); // 雙重檢查 if (classes == null) { synchronized (cachedClasses) { classes = cachedClasses.get(); if (classes == null) { // 加載所有的擴展類 classes = loadExtensionClasses(); cachedClasses.set(classes); } } } return classes; }
getExtensionClasses方法同樣是先從緩存中讀取,緩存不存在,在去加載:
private Map> loadExtensionClasses() { // 獲取擴展類SPI注解 final SPI defaultAnnotation = type.getAnnotation(SPI.class); if (defaultAnnotation != null) { String value = defaultAnnotation.value(); if ((value = value.trim()).length() > 0) { String[] names = NAME_SEPARATOR.split(value); if (names.length > 1) { throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names)); } if (names.length == 1) { cachedDefaultName = names[0]; } } } Map > extensionClasses = new HashMap >(); // 加載指定文件夾下的配置文件 loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName()); loadDirectory(extensionClasses, DUBBO_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName()); loadDirectory(extensionClasses, SERVICES_DIRECTORY, type.getName().replace("org.apache", "com.alibaba")); return extensionClasses; }
loadExtensionClasses 方法總共做了兩件事情,一是對 SPI 注解進行解析,二是調(diào)用 loadDirectory 方法加載指定文件夾配置文件。SPI 注解解析過程比較簡單,無需多說。下面我們來看一下 loadDirectory 做了哪些事情。
private void loadDirectory(Map> extensionClasses, String dir, String type) { // fileName = 文件夾路徑 + type 全限定名 String fileName = dir + type; try { Enumeration urls; ClassLoader classLoader = findClassLoader(); if (classLoader != null) { // 根據(jù)文件名加載所有的同名文件 urls = classLoader.getResources(fileName); } else { urls = ClassLoader.getSystemResources(fileName); } if (urls != null) { while (urls.hasMoreElements()) { java.net.URL resourceURL = urls.nextElement(); // 加載資源 loadResource(extensionClasses, classLoader, resourceURL); } } } catch (Throwable t) { logger.error("Exception when load extension class(interface: " + type + ", description file: " + fileName + ").", t); } }
loadDirectory 方法先通過 classLoader 獲取所有資源鏈接,然后再通過 loadResource 方法加載資源。我們繼續(xù)跟下去,看一下 loadResource 方法的實現(xiàn)。
private void loadResource(Map> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) { try { BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8")); try { String line; // 讀取文件中內(nèi)容 while ((line = reader.readLine()) != null) { final int ci = line.indexOf("#"); if (ci >= 0) { line = line.substring(0, ci); } line = line.trim(); if (line.length() > 0) { try { String name = null; int i = line.indexOf("="); if (i > 0) { // 以等于號 = 為界,截取鍵與值 name = line.substring(0, i).trim(); line = line.substring(i + 1).trim(); } if (line.length() > 0) { // 加載類,并通過 loadClass 方法對類進行緩存 loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name); } } catch (Throwable t) { IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t); exceptions.put(line, e); } } } } finally { reader.close(); } } catch (Throwable t) { logger.error("Exception when load extension class(interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL, t); } }
loadClass()方法主要是利用反射原理,根據(jù)類的權(quán)限定名加載成類,并存入緩存中
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/73240.html
摘要:對于這個矛盾的問題,通過自適應拓展機制很好的解決了。自適應拓展機制的實現(xiàn)邏輯比較復雜,首先會為拓展接口生成具有代理功能的代碼。 1、背景 在 Dubbo 中,很多拓展都是通過 SPI 機制進行加載的,比如 Protocol、Cluster、LoadBalance 等。有時,有些拓展并不想在框架啟動階段被加載,而是希望在拓展方法被調(diào)用時,根據(jù)運行時參數(shù)進行加載。這聽起來有些矛盾。拓展未被...
摘要:什么是類那什么樣類的才是擴展機制中的類呢類是一個有復制構(gòu)造函數(shù)的類,也是典型的裝飾者模式。代碼如下有一個參數(shù)是的復制構(gòu)造函數(shù)有一個構(gòu)造函數(shù),參數(shù)是擴展點,所以它是一個擴展機制中的類。 摘要:?在Dubbo可擴展機制實戰(zhàn)中,我們了解了Dubbo擴展機制的一些概念,初探了Dubbo中LoadBalance的實現(xiàn),并自己實現(xiàn)了一個LoadBalance。是不是覺得Dubbo的擴展機制很不錯呀...
摘要:今天我想聊聊的另一個很棒的特性就是它的可擴展性。的擴展機制在的官網(wǎng)上,描述自己是一個高性能的框架。接下來的章節(jié)中我們會慢慢揭開擴展機制的神秘面紗。擴展擴展點的實現(xiàn)類。的定義在配置文件中可以看到文件中定義了個的擴展實現(xiàn)。 摘要: 在Dubbo的官網(wǎng)上,Dubbo描述自己是一個高性能的RPC框架。今天我想聊聊Dubbo的另一個很棒的特性, 就是它的可擴展性。 Dubbo的擴展機制 在Dub...
摘要:二注解該注解為了保證在內(nèi)部調(diào)用具體實現(xiàn)的時候不是硬編碼來指定引用哪個實現(xiàn),也就是為了適配一個接口的多種實現(xiàn),這樣做符合模塊接口設計的可插拔原則,也增加了整個框架的靈活性,該注解也實現(xiàn)了擴展點自動裝配的特性。 Dubbo擴展機制SPI 前一篇文章《dubbo源碼解析(一)Hello,Dubbo》是對dubbo整個項目大體的介紹,而從這篇文章開始,我將會從源碼來解讀dubbo再各個模塊的實...
摘要:為了實現(xiàn)在模塊裝配的時候,不在模塊里寫死代碼,就需要一種服務發(fā)現(xiàn)機制。就提供了這樣一種機制為某個接口尋找服務實現(xiàn),有點類似思想,將裝配的控制權(quán)移到代碼之外。即接口文件的全類名。五示例遵循上述第一條第點,這里為接口文件,其中和為兩個實現(xiàn)類。 一、Dubbo內(nèi)核 Dubbo內(nèi)核主要包含SPI、AOP、IOC、Compiler。 二、JDK的SPI 1.spi的設計目標: 面向?qū)ο蟮脑O計里...
閱讀 484·2019-08-29 12:44
閱讀 3065·2019-08-26 17:49
閱讀 2545·2019-08-26 13:40
閱讀 1226·2019-08-26 13:39
閱讀 3709·2019-08-26 11:59
閱讀 1876·2019-08-26 10:59
閱讀 2545·2019-08-23 18:33
閱讀 2750·2019-08-23 18:30