真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

什么是DubboSPI機(jī)制

本篇內(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() {   ServiceLoader serviceLoader = 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() {   ExtensionLoader extensionLoader = 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 holder = cachedInstances.get(name);     if (holder == null) {         cachedInstances.putIfAbsent(name, new Holder());         holder = cachedInstances.get(name);     }     Object instance = holder.get();     // DCL     if (instance == null) {         synchronized (holder) {             instance = holder.get();             if (instance == null) {                 // 創(chuàng)建擴(kuò)展實(shí)例                 instance = createExtension(name);                 // 設(shè)置實(shí)例到 holder 中                 holder.set(instance);             }         }     }     return (T) instance; }

上面這一段代碼主要做的事情就是先檢查緩存,緩存不存在創(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í)!


網(wǎng)站欄目:什么是DubboSPI機(jī)制
當(dāng)前路徑:http://weahome.cn/article/iiopei.html

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部