Apache Dubbo的SPI機(jī)制是如何實(shí)現(xiàn)的
一、SPI
1.1 JDK自帶SPI實(shí)現(xiàn)
從JDK1.6開始引入SPI機(jī)制后,可以看到很多使用SPI的案例,比如最常見的數(shù)據(jù)庫驅(qū)動(dòng)實(shí)現(xiàn),在JDK中只定義了java.sql.Driver的接口,具體實(shí)現(xiàn)由各數(shù)據(jù)庫廠商來提供。下面一個(gè)簡單的例子來快速了解下Java SPI的使用方式:
1)定義一個(gè)接口
package com.vivo.study public interface Car { void getPrice(); }
2)接口實(shí)現(xiàn)
package com.vivo.study.impl /** * 實(shí)現(xiàn)一 * */ public class AudiCar implements Car { @Override public void getPrice() { System.out.println("Audi A6L's price is 500000 RMB."); } } package com.vivo.study.impl /** * 實(shí)現(xiàn)二 * */ public class BydCar implements Car { @Override public void getPrice() { System.out.println("BYD han's price is 220000 RMB."); } }
3)掛載擴(kuò)展類信息
在META-INF/services目錄下以接口全名為文件名的文本文件,對(duì)應(yīng)此處即在META-INF/services目錄下創(chuàng)建一個(gè)文件名為com.vivo.study.Car的文件,文件內(nèi)容如下:
com.vivo.study.impl.AudiCar com.vivo.study.impl.BydCar
4)使用
public class SpiDemo { public static void main(String[] args) { ServiceLoader<Car> load = ServiceLoader.load(Car.class); Iterator<Car> iterator = load.iterator(); while (iterator.hasNext()) { Car car = iterator.next(); car.getPrice(); } } }
上面的例子簡單的介紹了JDK SPI機(jī)制的使用方式,其中最關(guān)鍵的類為ServiceLoader,通過ServiceLoader類來加載接口的實(shí)現(xiàn)類,ServiceLoader是Iterable接口的實(shí)現(xiàn)類,對(duì)于ServiceLoader加載的詳細(xì)過程此處不展開。
JDK對(duì)SPI的加載實(shí)現(xiàn)存在一個(gè)較為突出的小缺點(diǎn),無法按需加載實(shí)現(xiàn)類,通過ServiceLoader.load加載時(shí)會(huì)將文件中的所有實(shí)現(xiàn)都進(jìn)行實(shí)例化,如果想要獲取具體某個(gè)具體的實(shí)現(xiàn)類需要進(jìn)行遍歷判斷。
1.2 Dubbo SPI
SPI擴(kuò)展是Dubbo的最大的優(yōu)點(diǎn)之一,支持協(xié)議擴(kuò)展、調(diào)用攔截?cái)U(kuò)展、引用監(jiān)聽擴(kuò)展等等。在Dubbo中,根據(jù)不同的擴(kuò)展定位,擴(kuò)展文件分別被放置在META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/這三個(gè)路徑下。
Dubbo中有直接使用JDK SPI實(shí)現(xiàn)的方式,比如org.apache.dubbo.common.extension.LoadingStrategy放在META-INF/services/路徑下,但大多情況下都是使用其自身對(duì)JDK SPI的實(shí)現(xiàn)的一種優(yōu)化方式,可稱為Dubbo SPI,也就是本文要講解的點(diǎn)。
相比于JDK的SPI的實(shí)現(xiàn),Dubbo SPI具有以下特點(diǎn):
配置形式更靈活:支持以key:value的形式在文件里配置類似name:xxx.xxx.xxx.xx,后續(xù)可以通過name來進(jìn)行擴(kuò)展類按需精準(zhǔn)獲取。
緩存的使用:使用緩存提升性能,保證一個(gè)擴(kuò)展實(shí)現(xiàn)類至多會(huì)加載一次。
對(duì)擴(kuò)展類細(xì)分?jǐn)U展:支持?jǐn)U展點(diǎn)自動(dòng)包裝(Wrapper)、擴(kuò)展點(diǎn)自動(dòng)裝配、擴(kuò)展點(diǎn)自適應(yīng)(@Adaptive)、擴(kuò)展點(diǎn)自動(dòng)激活(@Activate)。
Dubbo對(duì)擴(kuò)展點(diǎn)的加載主要由ExtensionLoader這個(gè)類展開。
二、加載-ExtensionLoader
ExtensionLoader在Dubbo里的角色類似ServiceLoader在JDK中的存在,用于加載擴(kuò)展類。在Dubbo源碼里,隨處都可以見到ExtensionLoader的身影,比如在服務(wù)暴露里的關(guān)鍵類ServiceConfig中等,弄清楚ExtensionLoader的實(shí)現(xiàn)細(xì)節(jié)對(duì)閱讀Dubbo源碼有很大的幫助。
2.1 獲取ExtensionLoader的實(shí)例
ExtensionLoader沒有提供共有的構(gòu)造函數(shù),
只能通過ExtensionLoader.getExtensionLoader(Class type)來獲取ExtensionLoader實(shí)例。
public // ConcurrentHashMap緩存,key -> Class value -> ExtensionLoader實(shí)例 private static final ConcurrentMap<Class<?>, ExtensionLoader<?>> EXTENSION_LOADERS = new ConcurrentHashMap<>(64); private ExtensionLoader(Class<?> type) { this.type = type; objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); } public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { if (type == null) { throw new IllegalArgumentException("Extension type == null"); } // 檢查是否是接口,如果不是則拋出異常 if (!type.isInterface()) { throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!"); } // 檢查接口是否是被@SPI注解修飾,如果沒有則拋出異常 if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); } // 從緩存里取,沒有則初始化一個(gè)并放入緩存EXTENSION_LOADERS中 ExtensionLoader<T> loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); if (loader == null) { EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader<T>(type)); loader = (ExtensionLoader<T>) EXTENSION_LOADERS.get(type); } return loader; } }
上面的代碼展示了獲取ExtensionLoader實(shí)例的過程,可以看出,每一個(gè)被@SPI修飾的接口都會(huì)對(duì)應(yīng)同一個(gè)ExtensionLoader實(shí)例,且對(duì)應(yīng)ExtensionLoader只會(huì)被初始化一次,并緩存在ConcurresntHashMap中。
2.2 加載擴(kuò)展類
加載擴(kuò)展類入口,當(dāng)使用ExtensionLoader時(shí),getExtensionName、getActivateExtension或是getDefaultExtension都要經(jīng)過getExtensionClasses方法來加載擴(kuò)展類,如下圖;
getExtensionClasses方法調(diào)用的路徑如下圖,getExtensionClasses是加載擴(kuò)展類的一個(gè)起點(diǎn),會(huì)首先從緩存中獲取,如果緩存中沒有則通過loadExtensionClasses方法來加載擴(kuò)展類,所以說實(shí)際上的加載邏輯入口在loadExtensionClasses。
getExtensionClasses |->loadExtensionClasses |->cacheDefaultExtensionName |->loadDirectory |->loadResource |->loadClass
2.2.1 loadExtensionClasses加載擴(kuò)展類
由于整個(gè)加載過程設(shè)計(jì)的源碼較多,因此用一個(gè)流程圖來進(jìn)行描述,具體細(xì)節(jié)可以結(jié)合源碼進(jìn)行查看。
loadExtensionClasses主要做了以下這幾件事:
默認(rèn)擴(kuò)展名:
抽取默認(rèn)擴(kuò)展實(shí)現(xiàn)名并緩存在ExtensionLoader里的cachedDefaultName,默認(rèn)擴(kuò)展名配置通過@SPI注解在接口上配置,如配置@SPI("defaultName")則默認(rèn)擴(kuò)展名為defaultName。
加載擴(kuò)展類信息:
從META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/這三個(gè)路徑下尋找以類的全路徑名命名的文件,并逐行讀取文件里的內(nèi)容。
加載class并緩存:
對(duì)擴(kuò)展類分為自適應(yīng)擴(kuò)展實(shí)現(xiàn)類(被@Adaptive修飾的實(shí)現(xiàn)類)、包裝類(擁有一個(gè)只有一個(gè)為這個(gè)接口類型的參數(shù)的構(gòu)造方法)、普通擴(kuò)展類,其中普通擴(kuò)展類中又包含自動(dòng)激活擴(kuò)展類(被@Activate修飾的類)和真普通的類,對(duì)自適應(yīng)擴(kuò)展實(shí)現(xiàn)類、包裝類、自動(dòng)激活擴(kuò)展類這三種類型的類分別加載并分別緩存到cachedAdaptiveClass、cachedActivates、cachedWrapperClasses。
返回結(jié)果Map<String, Class<?>>:
結(jié)果返回Map,其中key對(duì)應(yīng)擴(kuò)展文件里配置的name,value對(duì)應(yīng)擴(kuò)展的類class,最后在getExtensionClasses方法里會(huì)將此結(jié)果放入緩存cachedClasses中。此結(jié)果Map中包含除了自適應(yīng)擴(kuò)展實(shí)現(xiàn)類和包裝實(shí)現(xiàn)類的其他所用的擴(kuò)展類名與對(duì)應(yīng)類的映射關(guān)系。
通過loadExtensionClasses方法把擴(kuò)展類(Class對(duì)象)都加載到相應(yīng)的緩存中,是為了方便后面實(shí)例化擴(kuò)展類對(duì)象,通過newInstance()等方法來實(shí)例化。
2.2.2擴(kuò)展包裝類
什么是擴(kuò)展包裝類?是不是類名結(jié)尾包含了Wrapper的類就是擴(kuò)展包裝類?
在Dubbo SPI接口的實(shí)現(xiàn)擴(kuò)展類中,如果此類包含一個(gè)此接口作為參數(shù)的構(gòu)造方法,則為擴(kuò)展包裝類。擴(kuò)展包裝類的作用是持有具體的擴(kuò)展實(shí)現(xiàn)類,可以一層一層的被包裝,作用類似AOP。
包裝擴(kuò)展類的作用是類似AOP,方便擴(kuò)展增強(qiáng)。具體實(shí)現(xiàn)代碼如下:
從代碼中可以得出,可以通過boolean wrap選擇是否使用包裝類,默認(rèn)情況下為true;如果有擴(kuò)展包裝類,實(shí)際的實(shí)現(xiàn)類會(huì)被包裝類按一定的順序一層一層包起來。
如Protocol的實(shí)現(xiàn)類ProtocolFilterWrapper、ProtocolListenerWrapper都是擴(kuò)展包裝類。
2.2.3自適應(yīng)擴(kuò)展實(shí)現(xiàn)類
2.2.3.1 @Adaptive
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface Adaptive { String[] value() default {}; }
從源碼以及源碼注釋中可以得出以下幾點(diǎn):
Adaptive是一個(gè)注解,可以修飾類(接口,枚舉)和方法。
此注解的作用是為ExtensionLoader注入擴(kuò)展實(shí)例提供有用的信息。
從注釋中理解value的作用:
value可以決定選擇使用具體的擴(kuò)展類。
通過value配置的值key,在修飾的方法的入?yún)rg.apache.dubbo.common.URL中通過key獲取到對(duì)應(yīng)的值value,根據(jù)value作為extensionName去決定使用對(duì)應(yīng)的擴(kuò)展類。
如果通過2沒有找到對(duì)應(yīng)的擴(kuò)展,會(huì)選擇默認(rèn)的擴(kuò)展類,通過@SPI配置默認(rèn)擴(kuò)展類。
2.2.3.2 @Adaptive簡單例子
由于@Adaptive修飾類時(shí)比較好理解,這里舉一個(gè)@Adaptive修飾方法的例子,使用@Adaptive修飾方法的這種情況在Dubbo也是隨處可見。
/** * Dubbo SPI 接口 */ @SPI("impl1") public interface SimpleExt { @Adaptive({"key1", "key2"}) String yell(URL url, String s); }
如果調(diào)用
ExtensionLoader.getExtensionLoader(SimpleExt.class).getAdaptiveExtension().yell(url, s)方法,最終調(diào)用哪一個(gè)擴(kuò)展類的實(shí)例去執(zhí)行yell方法的流程大致為:先獲取擴(kuò)展類的名稱extName(對(duì)應(yīng)上面說的name:class中的name),然后通過extName來獲取對(duì)應(yīng)的類Class,再實(shí)例化進(jìn)行調(diào)用。所以關(guān)鍵的步驟在怎么得到extName,上面的這個(gè)例子得到extName的流程為:
通過url.getParameters.get("key1")獲取,
沒有獲取到則用url.getParameters.get("key2"),如果還是沒有獲取到則使用impl1對(duì)應(yīng)的實(shí)現(xiàn)類,最后還是沒有獲取到則拋異常IllegalStateException。
可以看出,@Adaptive的好處就是可以通過方法入?yún)Q定具體調(diào)用哪一個(gè)實(shí)現(xiàn)類。下面會(huì)對(duì)@Adaptive的具體實(shí)現(xiàn)進(jìn)行詳細(xì)分析。
2.2.3.3 @Adaptive加載流程
流程關(guān)鍵點(diǎn)說明:
1)黃色標(biāo)記的,cachedAdaptiveClass是在ExtensionLoader#loadClass方法中加載Extension類時(shí)緩存的。
2)綠色標(biāo)記的,如果Extension類中存在被@Adaptive修飾的類時(shí)會(huì)使用該類來初始化實(shí)例。
3)紅色標(biāo)記的,如果Extension類中不存在被@Adaptive修飾的類時(shí),則需要?jiǎng)討B(tài)生成代碼,通過javassist(默認(rèn))來編譯生成Xxxx$Adaptive類來實(shí)例化。
4)實(shí)例化后通過injectExtension來將Adaptive實(shí)例的Extension注入(屬性注入)。
后續(xù)圍繞上述的關(guān)鍵點(diǎn)3詳細(xì)展開,關(guān)鍵點(diǎn)4此處不展開。
動(dòng)態(tài)生成Xxx$Adaptive類:下面的代碼為動(dòng)態(tài)生成Adaptive類的相關(guān)代碼,具體生成代碼的細(xì)節(jié)在AdaptiveClassCodeGenerator#generate中
public class ExtensionLoader<T> { // ... private Class<?> getAdaptiveExtensionClass() { // 根據(jù)對(duì)應(yīng)的SPI文件加載擴(kuò)展類并緩存,細(xì)節(jié)此處不展開 getExtensionClasses(); // 如果存在被@Adaptive修飾的類則直接返回此類 if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } // 動(dòng)態(tài)生成Xxxx$Adaptive類 return cachedAdaptiveClass = createAdaptiveExtensionClass(); } private Class<?> createAdaptiveExtensionClass() { // 生成Xxxx$Adaptive類代碼,可自行加日志或斷點(diǎn)查看生成的代碼 String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate(); ClassLoader classLoader = findClassLoader(); // 獲取動(dòng)態(tài)編譯器,默認(rèn)為javassist org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); return compiler.compile(code, classLoader); } }
AdaptiveClassCodeGenerator#generate生成code的方式是通過字符串拼接,大量使用String.format,整個(gè)代碼過程比較繁瑣,可通過debug去了解細(xì)節(jié)。
最關(guān)鍵的的部分是生成被@Adaptive修飾的方法的內(nèi)容,也就是最終調(diào)用實(shí)例的@Adaptive方法時(shí),可通過參數(shù)來動(dòng)態(tài)選擇具體使用哪個(gè)擴(kuò)展實(shí)例。下面對(duì)此部分進(jìn)行分析:
public class AdaptiveClassCodeGenerator { // ... /** * generate method content */ private String generateMethodContent(Method method) { // 獲取方法上的@Adaptive注解 Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); StringBuilder code = new StringBuilder(512); if (adaptiveAnnotation == null) { // 方法時(shí)沒有@Adaptive注解,生成不支持的代碼 return generateUnsupported(method); } else { // 方法參數(shù)里URL是第幾個(gè)參數(shù),不存在則為-1 int urlTypeIndex = getUrlTypeIndex(method); // found parameter in URL type if (urlTypeIndex != -1) { // Null Point check code.append(generateUrlNullCheck(urlTypeIndex)); } else { // did not find parameter in URL type code.append(generateUrlAssignmentIndirectly(method)); } // 獲取方法上@Adaptive配置的value // 比如 @Adaptive({"key1","key2"}),則會(huì)返回String數(shù)組{"key1","key2"} // 如果@Adaptive沒有配置value,則會(huì)根據(jù)簡寫接口名按駝峰用.分割,比如SimpleExt對(duì)應(yīng)simple.ext String[] value = getMethodAdaptiveValue(adaptiveAnnotation); // 參數(shù)里是否存在org.apache.dubbo.rpc.Invocation boolean hasInvocation = hasInvocationArgument(method); code.append(generateInvocationArgumentNullCheck(method)); // 生成String extName = xxx;的代碼 ,extName用于獲取具體的Extension實(shí)例 code.append(generateExtNameAssignment(value, hasInvocation)); // check extName == null? code.append(generateExtNameNullCheck(value)); code.append(generateExtensionAssignment()); // return statement code.append(generateReturnAndInvocation(method)); } return code.toString(); } }
上述生成Adaptive類的方法內(nèi)容中最關(guān)鍵的步驟在生成extName的部分,也就是generateExtNameAssignment(value,hasInvocation),此方法if太多了(有點(diǎn)眼花繚亂)。
以下列舉幾個(gè)例子對(duì)此方法的實(shí)現(xiàn)流程進(jìn)行簡單展示:假設(shè)方法中的參數(shù)不包含org.apache.dubbo.rpc.Invocation,包含org.apache.dubbo.rpc.Invocation的情況會(huì)更加復(fù)雜。
1)方法被@Adaptive修飾,沒有配置value,且在接口@SPI上配置了默認(rèn)的實(shí)現(xiàn)
@SPI("impl1") public interface SimpleExt { @Adaptive String echo(URL url, String s); }
對(duì)應(yīng)生成extName的代碼為:
String extName = url.getParameter("simple.ext", "impl1")
2)方法被@Adaptive修飾,沒有配置value,且在接口@SPI上沒有配置默認(rèn)的實(shí)現(xiàn)
@SPI("impl1") public interface SimpleExt { @Adaptive({"key1", "key2"}) String yell(URL url, String s); }
對(duì)應(yīng)生成extName的代碼為:
String extName = url.getParameter( "simple.ext")
3)方法被@Adaptive修飾,配置了value(假設(shè)兩個(gè),依次類推),且在接口@SPI上配置了默認(rèn)的實(shí)現(xiàn)
@SPI public interface SimpleExt { @Adaptive({"key1", "key2"}) String yell(URL url, String s); }
對(duì)應(yīng)生成extName的代碼為:
String extName = url.getParameter("key1", url.getParameter("key2", "impl1"));
4)方法被@Adaptive修飾,配置了value(假設(shè)兩個(gè),依次類推),且在接口@SPI沒有配置默認(rèn)的實(shí)現(xiàn)
@SPI public interface SimpleExt { @Adaptive({"key1", "key2"}) String yell(URL url, String s); }
對(duì)應(yīng)生成extName的代碼為:
String extName = url.getParameter("key1", url.getParameter("key2"));
完整的生成類可參見附錄。
2.2.4自動(dòng)激活擴(kuò)展類
如果你有擴(kuò)展實(shí)現(xiàn)過Dubbo的Filter,那么一定會(huì)對(duì)@Activate很熟悉。@Activate注解的作用是可以通過給定的條件來自動(dòng)激活擴(kuò)展實(shí)現(xiàn)類,通過ExtensionLoader#getActivateExtension(URL,String, String)方法可以找到指定條件下需要激活的擴(kuò)展類列表。
下面以一個(gè)例子來對(duì)@Activate的作用進(jìn)行說明,在Consumer調(diào)用Dubbo接口時(shí),會(huì)經(jīng)過消費(fèi)方的過濾器鏈以及提供方的過濾器鏈,在Provider暴露服務(wù)的過程中會(huì)拼接需要使用哪些Filter。
對(duì)應(yīng)源碼中的位置在ProtocolFilterWrapper#buildInvokerChain(invoker, key, group)方法中。
// export:key-> service.filter ; group-> provider private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) { // 在Provider暴露服務(wù)服務(wù)export時(shí),會(huì)根據(jù)獲取Url中的service.filter對(duì)應(yīng)的值和group=provider來獲取激活對(duì)應(yīng)的Filter List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group); }
ExtensionLoader#getActivateExtension(URL, String, String)是怎么根據(jù)條件來自動(dòng)激活對(duì)應(yīng)的擴(kuò)展類列表的可以自行查看該方法的代碼,此處不展開。
三、總結(jié)
本文主要對(duì)Dubbo SPI機(jī)制的擴(kuò)展類加載過程通過ExtensionLoader類源碼來進(jìn)行總結(jié),可以概況為以下幾點(diǎn):
1.Dubbo SPI結(jié)合了JDK SPI的實(shí)現(xiàn),并在此基礎(chǔ)上進(jìn)行優(yōu)化,如精準(zhǔn)按需加載擴(kuò)展類、緩存提升性能。
2.分析ExtensionLoader加載擴(kuò)展類的過程,加載META-INF/dubbo/internal/,META-INF/dubbo/,META-INF/services/這三個(gè)路徑下的文件,并分類緩存在ExtensionLoader實(shí)例。
3.介紹擴(kuò)展包裝類及其實(shí)現(xiàn)過程,擴(kuò)展包裝類實(shí)現(xiàn)了類似AOP的功能。
4.自適應(yīng)擴(kuò)展類,分析@Adptive修飾方法時(shí)動(dòng)態(tài)生成Xxx$Adaptive類的過程,以及通過參數(shù)自適應(yīng)選擇擴(kuò)展實(shí)現(xiàn)類完成方法調(diào)用的案例介紹。
簡單介紹自動(dòng)激活擴(kuò)展類及@Activate的作用。
四、附錄
4.1 Xxx$Adaptive完整案例
@SPI接口定義
@SPI("impl1") public interface SimpleExt { // @Adaptive example, do not specify a explicit key. @Adaptive String echo(URL url, String s); @Adaptive({"key1", "key2"}) String yell(URL url, String s); // no @Adaptive String bang(URL url, int i); }
生成的Adaptive類代碼
package org.apache.dubbo.common.extension.ext1; import org.apache.dubbo.common.extension.ExtensionLoader; public class SimpleExt$Adaptive implements org.apache.dubbo.common.extension.ext1.SimpleExt { public java.lang.String yell(org.apache.dubbo.common.URL arg0, java.lang.String arg1) { if (arg0 == null) throw new IllegalArgumentException("url == null"); org.apache.dubbo.common.URL url = arg0; String extName = url.getParameter("key1", url.getParameter("key2", "impl1")); if (extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + url.toString() + ") use keys([key1, key2])"); org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt)ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName); return extension.yell(arg0, arg1); } public java.lang.String echo(org.apache.dubbo.common.URL arg0, java.lang.String arg1) { if (arg0 == null) throw new IllegalArgumentException("url == null"); org.apache.dubbo.common.URL url = arg0; String extName = url.getParameter("simple.ext", "impl1"); if (extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + url.toString() + ") use keys([simple.ext])"); org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt) ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class).getExtension(extName); return extension.echo(arg0, arg1); } public java.lang.String bang(org.apache.dubbo.common.URL arg0, int arg1) { throw new UnsupportedOperationException("The method public abstract java.lang.String org.apache.dubbo.common.extension.ext1.SimpleExt.bang(org.apache.dubbo.common.URL,int) of interface org.apache.dubbo.common.extension.ext1.SimpleExt is not adaptive method!"); } }
以上就是Apache Dubbo的SPI機(jī)制是如何實(shí)現(xiàn)的的詳細(xì)內(nèi)容,更多關(guān)于Apache Dubbo的SPI機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java語言實(shí)現(xiàn)簡單FTP軟件 輔助功能模塊FTP站點(diǎn)管理實(shí)現(xiàn)(12)
這篇文章主要為大家詳細(xì)介紹了Java語言實(shí)現(xiàn)簡單FTP軟,輔助功能模塊FTP站點(diǎn)管理的實(shí)現(xiàn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04java.mail實(shí)現(xiàn)發(fā)送郵件
這篇文章主要為大家詳細(xì)介紹了java.mail實(shí)現(xiàn)發(fā)送郵件,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-02-02使用HandlerMethodArgumentResolver用于統(tǒng)一獲取當(dāng)前登錄用戶
這篇文章主要介紹了使用HandlerMethodArgumentResolver用于統(tǒng)一獲取當(dāng)前登錄用戶實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12SpringCloud集成和使用OpenFeign的教程指南
在微服務(wù)架構(gòu)中,服務(wù)間的通信是至關(guān)重要的,SpringCloud作為一個(gè)功能強(qiáng)大的微服務(wù)框架,為我們提供了多種服務(wù)間通信的方式,其中,OpenFeign是一個(gè)聲明式的Web服務(wù)客戶端,它使得編寫Web服務(wù)客戶端變得更加簡單,本文將詳細(xì)介紹如何在SpringCloud項(xiàng)目中集成和使用OpenFeign2024-10-10JMagick實(shí)現(xiàn)基本圖像處理的類實(shí)例
這篇文章主要介紹了JMagick實(shí)現(xiàn)基本圖像處理的類,實(shí)例分析了java圖像處理的相關(guān)技巧,需要的朋友可以參考下2015-06-06Java中do-while循環(huán)的使用方法及注意事項(xiàng)詳解
這篇文章主要介紹了Java中do-while循環(huán)的使用方法及注意事項(xiàng)的相關(guān)資料,在Java編程中,do-while循環(huán)是一種基本的循環(huán)控制結(jié)構(gòu),它至少執(zhí)行一次循環(huán)體,然后根據(jù)條件判斷是否繼續(xù),文中將用法介紹的非常詳細(xì),需要的朋友可以參考下2024-10-10