本篇內(nèi)容主要講解“什么是Dubbo SPI機(jī)制”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“什么是Dubbo SPI機(jī)制”吧!
創(chuàng)新互聯(lián)是一家專(zhuān)業(yè)提供大渡口企業(yè)網(wǎng)站建設(shè),專(zhuān)注與成都網(wǎng)站制作、網(wǎng)站設(shè)計(jì)、HTML5建站、小程序制作等業(yè)務(wù)。10年已為大渡口眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專(zhuān)業(yè)網(wǎng)絡(luò)公司優(yōu)惠進(jìn)行中。
SPI是什么
SPI是一種簡(jiǎn)稱(chēng),全名叫 Service Provider Interface,Java本身提供了一套SPI機(jī)制,SPI 的本質(zhì)是將接口實(shí)現(xiàn)類(lèi)的全限定名配置在文件中,并由服務(wù)加載器讀取配置文件,加載實(shí)現(xiàn)類(lèi),這樣可以在運(yùn)行時(shí),動(dòng)態(tài)為接口替換實(shí)現(xiàn)類(lèi),這也是很多框架組件實(shí)現(xiàn)擴(kuò)展功能的一種手段。
而今天要說(shuō)的Dubbo SPI機(jī)制和Java SPI還是有一點(diǎn)區(qū)別的,Dubbo 并未使用 Java 原生的 SPI 機(jī)制,而是對(duì)他進(jìn)行了改進(jìn)增強(qiáng),進(jìn)而可以很容易地對(duì)Dubbo進(jìn)行功能上的擴(kuò)展。
學(xué)東西得帶著問(wèn)題去學(xué),我們先提幾個(gè)問(wèn)題,再接著看
1.什么是SPI(開(kāi)頭已經(jīng)解釋了)
2.Dubbo SPI和Java原生的有什么區(qū)別
3.兩種實(shí)現(xiàn)應(yīng)該如何寫(xiě)出來(lái)
Java SPI是如何實(shí)現(xiàn)的
先定義一個(gè)接口:
public interface Car { void startUp(); }
然后創(chuàng)建兩個(gè)類(lèi),都實(shí)現(xiàn)這個(gè)Car接口
public class Truck implements Car{ @Override public void startUp() { System.out.println("The truck started"); } } public class Train implements Car{ @Override public void startUp() { System.out.println("The train started"); } }
然后在項(xiàng)目META-INF/services文件夾下創(chuàng)建一個(gè)名稱(chēng)為接口的全限定名,com.example.demo.spi.Car。
文件內(nèi)容寫(xiě)上實(shí)現(xiàn)類(lèi)的全限定名,如下:
com.example.demo.spi.Train com.example.demo.spi.Truck
最后寫(xiě)一個(gè)測(cè)試代碼:
public class JavaSPITest { @Test public void testCar() { ServiceLoaderserviceLoader = ServiceLoader.load(Car.class); serviceLoader.forEach(Car::startUp); } }
執(zhí)行完的輸出結(jié)果:
The train started The truck started
Dubbo SPI是如何實(shí)現(xiàn)的
Dubbo 使用的SPI并不是Java原生的,而是重新實(shí)現(xiàn)了一套,其主要邏輯都在ExtensionLoader類(lèi)中,邏輯也不難,后面會(huì)稍帶講一下
看看使用,和Java的差不了太多,基于前面的例子來(lái)看下,接口類(lèi)需要加上@SPI注解:
@SPI public interface Car { void startUp(); }
實(shí)現(xiàn)類(lèi)不需要改動(dòng)
配置文件需要放在META-INF/dubbo下面,配置寫(xiě)法有些區(qū)別,直接看代碼:
train = com.example.demo.spi.Train truck = com.example.demo.spi.Truck
最后就是測(cè)試類(lèi)了,先看代碼:
public class JavaSPITest { @Test public void testCar() { ExtensionLoaderextensionLoader = ExtensionLoader.getExtensionLoader(Car.class); Car car = extensionLoader.getExtension("train"); car.startUp(); } }
執(zhí)行結(jié)果:
The train started
Dubbo SPI中常用的注解
@SPI 標(biāo)記為擴(kuò)展接口
@Adaptive自適應(yīng)拓展實(shí)現(xiàn)類(lèi)標(biāo)志
@Activate 自動(dòng)激活條件的標(biāo)記
總結(jié)一下兩者區(qū)別:
使用上的區(qū)別Dubbo使用ExtensionLoader而不是ServiceLoader了,其主要邏輯都封裝在這個(gè)類(lèi)中
配置文件存放目錄不一樣,Java的在META-INF/services,Dubbo在META-INF/dubbo,META-INF/dubbo/internal
Java SPI 會(huì)一次性實(shí)例化擴(kuò)展點(diǎn)所有實(shí)現(xiàn),如果有擴(kuò)展實(shí)現(xiàn)初始化很耗時(shí),并且又用不上,會(huì)造成大量資源被浪費(fèi)
Dubbo SPI 增加了對(duì)擴(kuò)展點(diǎn) IOC 和 AOP 的支持,一個(gè)擴(kuò)展點(diǎn)可以直接 setter 注入其它擴(kuò)展點(diǎn)
Java SPI加載過(guò)程失敗,擴(kuò)展點(diǎn)的名稱(chēng)是拿不到的。比如:JDK 標(biāo)準(zhǔn)的 ScriptEngine,getName() 獲取腳本類(lèi)型的名稱(chēng),如果 RubyScriptEngine 因?yàn)樗蕾嚨?jruby.jar 不存在,導(dǎo)致 RubyScriptEngine 類(lèi)加載失敗,這個(gè)失敗原因是不會(huì)有任何提示的,當(dāng)用戶執(zhí)行 ruby 腳本時(shí),會(huì)報(bào)不支持 ruby,而不是真正失敗的原因
前面的3個(gè)問(wèn)題是不是已經(jīng)能回答出來(lái)了?是不是非常簡(jiǎn)單
Dubbo SPI源碼分析
Dubbo SPI使用上是通過(guò)ExtensionLoader的getExtensionLoader方法獲取一個(gè) ExtensionLoader 實(shí)例,然后再通過(guò) ExtensionLoader 的 getExtension 方法獲取拓展類(lèi)對(duì)象。這其中,getExtensionLoader 方法用于從緩存中獲取與拓展類(lèi)對(duì)應(yīng)的 ExtensionLoader,如果沒(méi)有緩存,則創(chuàng)建一個(gè)新的實(shí)例,直接上代碼:
public T getExtension(String name) { if (name == null || name.length() == 0) { throw new IllegalArgumentException("Extension name == null"); } if ("true".equals(name)) { // 獲取默認(rèn)的拓展實(shí)現(xiàn)類(lèi) return getDefaultExtension(); } // 用于持有目標(biāo)對(duì)象 Holder
上面這一段代碼主要做的事情就是先檢查緩存,緩存不存在創(chuàng)建擴(kuò)展對(duì)象
接下來(lái)我們看看創(chuàng)建的過(guò)程:
private T createExtension(String name) { // 從配置文件中加載所有的擴(kuò)展類(lèi),可得到“配置項(xiàng)名稱(chēng)”到“配置類(lèi)”的映射關(guān)系表 Class> clazz = getExtensionClasses().get(name); if (clazz == null) { throw findException(name); } try { T instance = (T) EXTENSION_INSTANCES.get(clazz); if (instance == null) { // 反射創(chuàng)建實(shí)例 EXTENSION_INSTANCES.putIfAbsent(clazz, clazz.newInstance()); instance = (T) EXTENSION_INSTANCES.get(clazz); } // 向?qū)嵗凶⑷胍蕾?nbsp; injectExtension(instance); Set> wrapperClasses = cachedWrapperClasses; if (wrapperClasses != null && !wrapperClasses.isEmpty()) { // 循環(huán)創(chuàng)建 Wrapper 實(shí)例 for (Class> wrapperClass : wrapperClasses) { // 將當(dāng)前 instance 作為參數(shù)傳給 Wrapper 的構(gòu)造方法,并通過(guò)反射創(chuàng)建 Wrapper 實(shí)例。 // 然后向 Wrapper 實(shí)例中注入依賴,最后將 Wrapper 實(shí)例再次賦值給 instance 變量 instance = injectExtension( (T) wrapperClass.getConstructor(type).newInstance(instance)); } } return instance; } catch (Throwable t) { throw new IllegalStateException("Extension instance (name: " + name + ", class: " + type + ") couldn't be instantiated: " + t.getMessage(), t); } }
這段代碼看著繁瑣,其實(shí)也不難,一共只做了4件事情:
1.通過(guò)getExtensionClasses獲取所有配置擴(kuò)展類(lèi)
2.反射創(chuàng)建對(duì)象
3.給擴(kuò)展類(lèi)注入依賴
4.將擴(kuò)展類(lèi)對(duì)象包裹在對(duì)應(yīng)的Wrapper對(duì)象里面
我們?cè)谕ㄟ^(guò)名稱(chēng)獲取擴(kuò)展類(lèi)之前,首先需要根據(jù)配置文件解析出擴(kuò)展類(lèi)名稱(chēng)到擴(kuò)展類(lèi)的映射關(guān)系表,之后再根據(jù)擴(kuò)展項(xiàng)名稱(chēng)從映射關(guān)系表中取出相應(yīng)的拓展類(lèi)即可。相關(guān)過(guò)程的代碼如下:
private Map> getExtensionClasses() { // 從緩存中獲取已加載的拓展類(lèi) Map > classes = cachedClasses.get(); // DCL if (classes == null) { synchronized (cachedClasses) { classes = cachedClasses.get(); if (classes == null) { // 加載擴(kuò)展類(lèi) classes = loadExtensionClasses(); cachedClasses.set(classes); } } } return classes; }
這里也是先檢查緩存,若緩存沒(méi)有,則通過(guò)一次雙重鎖檢查緩存,判空。此時(shí)如果 classes 仍為 null,則通過(guò) loadExtensionClasses 加載拓展類(lèi)。下面是 loadExtensionClasses 方法的代碼
private Map> loadExtensionClasses() { // 獲取 SPI 注解,這里的 type 變量是在調(diào)用 getExtensionLoader 方法時(shí)傳入的 final SPI defaultAnnotation = type.getAnnotation(SPI.class); if (defaultAnnotation != null) { String value = defaultAnnotation.value(); if ((value = value.trim()).length() > 0) { // 對(duì) SPI 注解內(nèi)容進(jìn)行切分 String[] names = NAME_SEPARATOR.split(value); // 檢測(cè) SPI 注解內(nèi)容是否合法,不合法則拋出異常 if (names.length > 1) { throw new IllegalStateException("more than 1 default extension name on extension..."); } // 設(shè)置默認(rèn)名稱(chēng),參考 getDefaultExtension 方法 if (names.length == 1) { cachedDefaultName = names[0]; } } } Map > extensionClasses = new HashMap >(); // 加載指定文件夾下的配置文件 loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY); loadDirectory(extensionClasses, DUBBO_DIRECTORY); loadDirectory(extensionClasses, SERVICES_DIRECTORY); return extensionClasses; }
loadExtensionClasses 方法總共做了兩件事情,一是對(duì) SPI 注解進(jìn)行解析,二是調(diào)用 loadDirectory 方法加載指定文件夾配置文件。SPI 注解解析過(guò)程比較簡(jiǎn)單,無(wú)需多說(shuō)。下面我們來(lái)看一下 loadDirectory 做了哪些事情
private void loadDirectory(Map> extensionClasses, String dir) { // fileName = 文件夾路徑 + type 全限定名 String fileName = dir + type.getName(); try { Enumeration urls; ClassLoader classLoader = findClassLoader(); // 根據(jù)文件名加載所有的同名文件 if (classLoader != null) { 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 occurred when loading extension class (interface: " + type + ", description file: " + fileName + ").", t); } }
loadDirectory 方法先通過(guò) classLoader 獲取所有資源鏈接,然后再通過(guò) loadResource 方法加載資源。我們繼續(xù)跟下去,看一下 loadResource 方法的實(shí)現(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) { // 截取 # 之前的字符串,# 之后的內(nèi)容為注釋?zhuān)枰雎?nbsp; line = line.substring(0, ci); } line = line.trim(); if (line.length() > 0) { try { String name = null; int i = line.indexOf('='); if (i > 0) { // 以等于號(hào) = 為界,截取鍵與值 name = line.substring(0, i).trim(); line = line.substring(i + 1).trim(); } if (line.length() > 0) { // 加載類(lèi),并通過(guò) loadClass 方法對(duì)類(lèi)進(jìn)行緩存 loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name); } } catch (Throwable t) { IllegalStateException e = new IllegalStateException("Failed to load extension class..."); } } } } finally { reader.close(); } } catch (Throwable t) { logger.error("Exception when load extension class..."); } }
loadResource 方法用于讀取和解析配置文件,并通過(guò)反射加載類(lèi),最后調(diào)用 loadClass 方法進(jìn)行其他操作。loadClass 方法用于主要用于操作緩存,該方法的邏輯如下:
private void loadClass(Map> extensionClasses, java.net.URL resourceURL, Class> clazz, String name) throws NoSuchMethodException { if (!type.isAssignableFrom(clazz)) { throw new IllegalStateException("..."); } // 檢測(cè)目標(biāo)類(lèi)上是否有 Adaptive 注解 if (clazz.isAnnotationPresent(Adaptive.class)) { if (cachedAdaptiveClass == null) { // 設(shè)置 cachedAdaptiveClass緩存 cachedAdaptiveClass = clazz; } else if (!cachedAdaptiveClass.equals(clazz)) { throw new IllegalStateException("..."); } // 檢測(cè) clazz 是否是 Wrapper 類(lèi)型 } else if (isWrapperClass(clazz)) { Set > wrappers = cachedWrapperClasses; if (wrappers == null) { cachedWrapperClasses = new ConcurrentHashSet >(); wrappers = cachedWrapperClasses; } // 存儲(chǔ) clazz 到 cachedWrapperClasses 緩存中 wrappers.add(clazz); // 程序進(jìn)入此分支,表明 clazz 是一個(gè)普通的拓展類(lèi) } else { // 檢測(cè) clazz 是否有默認(rèn)的構(gòu)造方法,如果沒(méi)有,則拋出異常 clazz.getConstructor(); if (name == null || name.length() == 0) { // 如果 name 為空,則嘗試從 Extension 注解中獲取 name,或使用小寫(xiě)的類(lèi)名作為 name name = findAnnotationName(clazz); if (name.length() == 0) { throw new IllegalStateException("..."); } } // 切分 name String[] names = NAME_SEPARATOR.split(name); if (names != null && names.length > 0) { Activate activate = clazz.getAnnotation(Activate.class); if (activate != null) { // 如果類(lèi)上有 Activate 注解,則使用 names 數(shù)組的第一個(gè)元素作為鍵, // 存儲(chǔ) name 到 Activate 注解對(duì)象的映射關(guān)系 cachedActivates.put(names[0], activate); } for (String n : names) { if (!cachedNames.containsKey(clazz)) { // 存儲(chǔ) Class 到名稱(chēng)的映射關(guān)系 cachedNames.put(clazz, n); } Class> c = extensionClasses.get(n); if (c == null) { // 存儲(chǔ)名稱(chēng)到 Class 的映射關(guān)系 extensionClasses.put(n, clazz); } else if (c != clazz) { throw new IllegalStateException("..."); } } } } }
綜上,loadClass方法操作了不同的緩存,比如cachedAdaptiveClass、cachedWrapperClasses和cachedNames等等
到這里基本上關(guān)于緩存類(lèi)加載的過(guò)程就分析完了,其他邏輯不難,認(rèn)真地讀下來(lái)加上Debug一下都能看懂的。
到此,相信大家對(duì)“什么是Dubbo SPI機(jī)制”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!