Dubbo提供了一種SPI的機制用於動態的加載擴展類,可是如何在運行時動態的選用哪種擴展類來提供服務,這就須要一種機制來進行動態的匹配。Dubbo SPI中提供的Adaptive機制就爲解決這個問題提供了一種良好的解決方案,本文首先會經過一個示例來說解Adaptive機制的用法,而後會從源碼的角度對其實現原理進行講解。java
對應於Adaptive機制,Dubbo提供了一個註解@Adaptive
,該註解能夠用於接口的某個子類上,也能夠用於接口方法上。若是用在接口的子類上,則表示Adaptive機制的實現會按照該子類的方式進行自定義實現;若是用在方法上,則表示Dubbo會爲該接口自動生成一個子類,而且按照必定的格式重寫該方法,而其他沒有標註@Adaptive
註解的方法將會默認拋出異常。對於第一種Adaptive的使用方式,Dubbo裏只有ExtensionFactory
接口使用了,其有一個子類AdaptiveExtensionFactory
就使用了@Adaptive
註解進行了標註,主要做用就是在獲取目標對象時,分別經過ExtensionLoader
和Spring容器
兩種方式獲取,該類的實現原理比較簡單,讀者可自行閱讀其源碼,本文主要講解將@Adaptive
註解標註在接口方法上以實現Adaptive機制的使用原理。這裏咱們以一個"水果種植者"的示例來進行講解,水果種植者能夠種植諸如蘋果和香蕉等水果。這裏咱們首先定義一個蘋果種植者的接口:apache
@SPI("apple") public interface FruitGranter { Fruit grant(); @Adaptive String watering(URL url); }
這裏須要注意的是,若是要使用Dubbo的SPI的支持,必須在目標接口上使用@SPI
註解進行標註,後面的值提供了一個默認值,也就是說若是沒有自定義的指定使用哪一個子類,那麼就使用該值所指定的子類。在該接口中,咱們將watering()
方法使用@Adaptive
註解進行了標註,表示該方法在自動生成的子類中是須要動態實現的方法。下面是咱們爲FruitGranter
提供的兩個實現類:數組
// 蘋果種植者 public class AppleGranter implements FruitGranter { @Override public Fruit grant() { return new Apple(); } @Override public String watering(URL url) { System.out.println("watering apple"); return "watering finished"; } }
// 香蕉種植者 public class BananaGranter implements FruitGranter { @Override public Fruit grant() { return new Banana(); } @Override public String watering(URL url) { System.out.println("watering banana"); return "watering success"; } }
這裏提供了AppleGranter
和BananaGranter
表示的實際上是兩種基礎服務類,本質上它們三者的關係是FruitGranter
用於對外提供一個規範,而AppleGranter
和BananaGranter
則是實現了這種規範的兩種基礎服務。至於調用方須要使用哪一種基礎服務來實現其功能,這就須要根據調用方指定的參數來動態選取的,而@Adaptive
機制就是提供了這樣一種選取功能。緩存
在Dubbo的SPI中,咱們指定了上述兩種服務類以後,須要在META-INF/dubbo
下建立一個文件,該文件的名稱是目標接口的全限定名,這裏是org.apache.dubbo.demo.example.eg19.FruitGranter
,在該文件中須要指定該接口全部可提供服務的子類,形式如:app
apple=org.apache.dubbo.demo.example.eg19.AppleGranter banana=org.apache.dubbo.demo.example.eg19.BananaGranter
文件中每個子類都有一個key與之對應,這個key也就是前面@SPI
註解後面所指定的值,也就是說這裏若是調用方沒有自定義指定使用哪一個子類,那麼默認就會使用AppleGranter
來提供服務。下面咱們來看一下調用方代碼如何實現:ide
public class ExtensionLoaderTest { @Test public void testGetExtensionLoader() { // 首先建立一個模擬用的URL對象 URL url = URL.valueOf("dubbo://192.168.0.101:20880?fruit.granter=apple"); // 經過ExtensionLoader獲取一個FruitGranter對象 FruitGranter granter = ExtensionLoader.getExtensionLoader(FruitGranter.class) .getAdaptiveExtension(); // 使用該FruitGranter調用其"自適應標註的"方法,獲取調用結果 String result = granter.watering(url); System.out.println(result); } }
上述代碼中,咱們首先模擬構造了一個URL對象,這個URL對象是Dubbo中進行參數傳遞所使用的一個基礎類,在配置文件中配置的屬性都會被封裝到該對象中。這裏咱們主要要注意該對象是經過一個url構造的,而且url的最後咱們有一個參數fruit.granter=apple
,這裏其實就是咱們所指定的使用哪一種基礎服務類的參數。好比這裏指定的就是使用apple
對應的AppleGranter
。ui
在構造一個URL對象以後,咱們經過ExtensionLoader.getExtensionLoader(FruitGranter.class)
方法獲取了一個FruitGranter
對應的ExtensionLoader
對象,而後調用其getAdaptiveExtension()
方法獲取其爲FruitGranter
接口構造的子類實例,這裏的子類實際上就是ExtensionLoader
經過必定的規則爲FruitGranter
接口編寫的子類代碼,而後經過javassist
或jdk
編譯加載這段代碼,加載完成以後經過反射構造其實例,最後將其實例返回。在上面咱們調用該實例,也就是granter對象的watering()
方法時,該方法內部就會經過url對象指定的參數來選擇具體的實例,從而將真正的工做交給該實例進行。經過這種方式,Dubbo SPI就實現了根據傳入參數動態的選用具體的實例來提供服務的功能。以下是該ExtensionLoader
爲FruitGranter
動態生成的子類代碼:url
package org.apache.dubbo.demo.example.eg19; import org.apache.dubbo.common.extension.ExtensionLoader; public class FruitGranter$Adaptive implements org.apache.dubbo.demo.example.eg19.FruitGranter { public org.apache.dubbo.demo.example.eg19.Fruit grant() { throw new UnsupportedOperationException( "The method public abstract org.apache.dubbo.demo.example.eg19.Fruit " + "org.apache.dubbo.demo.example.eg19.FruitGranter.grant() of interface " + "org.apache.dubbo.demo.example.eg19.FruitGranter is not adaptive method!"); } public java.lang.String watering(org.apache.dubbo.common.URL arg0) { if (arg0 == null) { throw new IllegalArgumentException("url == null"); } org.apache.dubbo.common.URL url = arg0; String extName = url.getParameter("fruit.granter", "apple"); if (extName == null) { throw new IllegalStateException( "Failed to get extension (org.apache.dubbo.demo.example.eg19.FruitGranter) name " + "from url (" + url.toString() + ") use keys([fruit.granter])"); } org.apache.dubbo.demo.example.eg19.FruitGranter extension = (org.apache.dubbo.demo.example.eg19.FruitGranter) ExtensionLoader .getExtensionLoader(org.apache.dubbo.demo.example.eg19.FruitGranter.class) .getExtension(extName); return extension.watering(arg0); } }
關於該生成的代碼,咱們主要要注意以下幾個問題:.net
@Adaptive
註解標註的接口方法,默認都會拋出異常;@Adaptive
註解標註的方法中,其參數中必須有一個參數類型爲URL,或者其某個參數提供了某個方法,該方法能夠返回一個URL對象;@SPI
註解中指定了默認值,那麼在使用URL對象獲取參數值時,若是沒有取到,就會使用該默認值;ExtensionLoader
中獲取該參數值對應的服務提供類對象,而後將真正的調用委託給該服務提供類對象進行;@Adaptive
註解的參數值中獲取,若是該註解沒有指定參數名,那麼就會默認將目標接口的類名轉換爲點分形式做爲參數名,好比這裏FruitGranter
轉換爲點分形式就是fruit.granter
。Dubbo Adaptive的實現機制根據上面的講解其實步驟已經比較清晰了,主要分爲以下三個步驟:debug
@Adaptive
註解的接口,若是不存在,則不支持Adaptive機制; 能夠看到,經過這種方式,Dubbo就實現了一種經過配置參數動態選擇所使用的服務的目的,而實現這種機制的入口主要在ExtensionLoader.getAdaptiveExtension()
方法,以下是該方法的實現:
public T getAdaptiveExtension() { Object instance = cachedAdaptiveInstance.get(); if (instance == null) { if (createAdaptiveInstanceError == null) { synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); if (instance == null) { try { // 建立Adaptive實例 instance = createAdaptiveExtension(); cachedAdaptiveInstance.set(instance); } catch (Throwable t) { createAdaptiveInstanceError = t; throw new IllegalStateException("Failed to create adaptive " + "instance: " + t.toString(), t); } } } } else { throw new IllegalStateException("Failed to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); } } return (T) instance; }
上面的代碼比較簡單,其實就是首先經過雙檢查法來從緩存中獲取Adaptive實例,若是沒獲取到,則建立一個。咱們這裏繼續看createAdaptiveExtension()
方法的實現:
private T createAdaptiveExtension() { try { return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e); } }
這裏建立Adaptive實例的方法是一個主幹方法,從這裏調用方法的順序就能夠看出其主要做用:
上面的經過setter方法注入屬性的方法比較簡單,主要是經過反射讀取相關的參數,而後分別在Dubbo的SPI和Spring容器中查找對應的bean,而且將其注入進來,這段代碼比較簡單,讀者可自行閱讀。咱們這裏主要關注Dubbo如何建立Adaptive類對象,也即getAdaptiveExtensionClass()
方法的實現:
private Class<?> getAdaptiveExtensionClass() { // 經過讀取Dubbo的配置文件,獲取其中的SPI類,其主要處理了四部分的類: // 1. 標註了@Activate註解的類,該註解的主要做用是將某個實現子類標註爲自動激活,也就是在加載 // 實例的時候也會加載該類的對象; // 2. 記錄目標接口是否標註了@Adaptive註解,若是標註了該註解,則表示須要爲該接口動態生成子類,或者說 // 目標接口是否存在標註了@Adaptive註解的子類,若是存在,則直接使用該子類做爲Adaptive類; // 3. 檢查加載到的類是否包含有傳入目標接口參數的構造方法,若是是,則表示該類是一個代理類,也能夠 // 將其理解爲最終會被做爲責任鏈進行調用的類,這些類最終會在目標類被調用的時候以相似於AOP的方式, // 將目標類包裹起來,而後將包裹以後的類對外提供服務; // 4. 剩餘的通常類就是實現了目標接口,而且做爲基礎服務提供的類。 getExtensionClasses(); // 通過上面的類加載過程,若是目標接口某個子類存在@Adaptive註解,就會將其class對象緩存到 // cachedAdaptiveClass對象中。這裏咱們就能夠看到@Adaptive註解的兩種使用方式的分界點,也就是說, // 若是某個子類標註了@Adaptive註解,那麼就會使用該子類所自定義的Adaptive機制,若是沒有子類標註了 // 該註解,那麼就會使用下面的createAdaptiveExtensionClass()方式來建立一個目標類class對象 if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } // 建立一個目標接口的子類class對象 return cachedAdaptiveClass = createAdaptiveExtensionClass(); } private Class<?> createAdaptiveExtensionClass() { // 爲目標接口生成子類代碼,以字符串形式表示 String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate(); // 獲取classloader ClassLoader classLoader = findClassLoader(); // 經過jdk或者javassist的方式編譯生成的子類字符串,從而獲得一個class對象 org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader( org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); return compiler.compile(code, classLoader); }
上面的代碼中主要是一個骨架代碼,首先經過getExtensionClasses()
獲取配置文件中配置的各個類對象,其加載的原理讀者可閱讀本人前面的文章Dubbo之SPI原理詳解;加載完成後,會經過AdaptiveClassCodeGenerator
來爲目標類生成子類代碼,並以字符串的形式返回,最後經過javassist或jdk的方式進行編譯而後返回class對象。這裏咱們主要閱讀AdaptiveClassCodeGenerator.generate()
方法是如何生成目標接口的子類的:
public String generate() { // 判斷目標接口是否有方法標註了@Adaptive註解,若是沒有則拋出異常 if (!hasAdaptiveMethod()) { throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!"); } StringBuilder code = new StringBuilder(); code.append(generatePackageInfo()); // 生成package信息 // 生成import信息,這裏只導入了ExtensionLoader類,其他的類都經過全限定名的方式來使用 code.append(generateImports()); code.append(generateClassDeclaration()); // 生成類聲明信息 Method[] methods = type.getMethods(); for (Method method : methods) { code.append(generateMethod(method)); // 爲各個方法生成實現方法信息 } code.append("}"); if (logger.isDebugEnabled()) { logger.debug(code.toString()); } return code.toString(); // 返回生成的class代碼 }
這裏generate()
方法是生成目標類的主幹方法,其主要分爲以下幾個步驟:
這裏前面幾個步驟實現原理都相較比較簡單,咱們繼續閱讀generateMethod()
方法的實現原理:
private String generateMethod(Method method) { String methodReturnType = method.getReturnType().getCanonicalName(); // 生成返回值信息 String methodName = method.getName(); // 生成方法名信息 String methodContent = generateMethodContent(method); // 生成方法體信息 String methodArgs = generateMethodArguments(method); // 生成方法參數信息 String methodThrows = generateMethodThrows(method); // 生成異常信息 return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent); // 對方法進行格式化返回 }
能夠看到,方法的生成,也拆分紅了幾個子步驟,主要包括:
須要注意的是,這裏所使用的全部類都是使用的其全限定類名,經過前面咱們展現的FruitGranter
的子類代碼也能夠看出這一點。上面生成的信息中,方法的返回值,方法名,方法參數以及異常信息均可以經過接口聲明獲取到,而方法體則須要根據必定的邏輯來生成。關於方法參數,須要說明的是,Dubbo並無使用接口中對應參數的名稱,而是對每個參數的參數名依次使用arg0
、arg1
等名稱,後續在閱讀代碼時讀者須要注意這一點。這裏咱們繼續閱讀Dubbo生成方法體內容的代碼:
private String generateMethodContent(Method method) { // 獲取方法上標註的@Adaptive註解,前面講到,Dubbo會使用該註解的值做爲動態參數的key值 Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); StringBuilder code = new StringBuilder(512); if (adaptiveAnnotation == null) { // 若是當前方法沒有標註@Adaptive註解,該方法的實現就會默認拋出異常 return generateUnsupported(method); } else { // 獲取參數中類型爲URL的參數所在的參數索引位置,由於咱們的參數都是經過arg[i]的形式編排的,於是 // 獲取其索引就能夠獲得該參數的引用。這裏URL參數的主要做用是獲取目標參數對應的參數值 int urlTypeIndex = getUrlTypeIndex(method); if (urlTypeIndex != -1) { // 若是參數中存在URL類型的參數,那麼就爲該參數進行空值檢查,若是爲空,則拋出異常 code.append(generateUrlNullCheck(urlTypeIndex)); } else { // 若是參數中不存在URL類型的參數,那麼就會檢查每一個參數,判斷其是否有某個方法的返回值是URL類型, // 若是存在該方法,則首先對該參數進行空指針檢查,若是爲空則拋出異常。而後調用該對象的目標方法, // 以獲取到一個URL對象,而後對獲取到的URL對象進行空值檢查,爲空也會拋出異常。 code.append(generateUrlAssignmentIndirectly(method)); } // 這裏主要是獲取@Adaptive註解的參數,若是沒有配置,就會使用目標接口的類型由駝峯形式轉換爲點分形式 // 的名稱做爲將要獲取的參數值的key名稱,好比前面的FruitGranter轉換後爲fruit.granter。 // 這裏須要注意的是,返回值是一個數組類型,這是由於Dubbo會經過嵌套獲取的方式來的到目標參數, // 好比咱們使用了@Adaptive({"client", "transporter"})的形式,那麼最終就會在URL對象中獲取兩次 // 參數,如String extName = url.getParameter("client", url.getParameter("transporter")) String[] value = getMethodAdaptiveValue(adaptiveAnnotation); // 判斷是否存在Invocation類型的參數 boolean hasInvocation = hasInvocationArgument(method); // 爲Invocation類型的參數添加空值檢查的邏輯 code.append(generateInvocationArgumentNullCheck(method)); // 生成獲取extName的邏輯,也即前面經過String[] value生成的經過url.getParameter()的 // 邏輯代碼,最終會獲得用戶配置的擴展的名稱,從而對應某個基礎服務類 code.append(generateExtNameAssignment(value, hasInvocation)); // 爲extName添加空值檢查代碼 code.append(generateExtNameNullCheck(value)); // 經過extName在ExtensionLoader中獲取其對應的基礎服務類,好比前面的FruitGranter,在這裏就是 // FruitGranter extension = ExtensionLoader.getExtensionLoader(ExtensionLoader.class) // .getExtension(extName),這樣就獲得了一個FruitGranter的實例對象 code.append(generateExtensionAssignment()); // 生成目標實例的當前方法的調用邏輯,而後將結果返回。好比FruitGranter就是 // return extension.watering(arg0); // 這裏方法名就是當前實現的方法的名稱,而參數就是當前方法傳入的參數,讀者不要忘記了當前方法 // 就是目標接口中的同一方法,而方法參數前面已經講到,都是使用arg[i]的形式命名的,於是這裏直接 // 將其依次羅列出來便可 code.append(generateReturnAndInovation(method)); } // 將生成的代碼返回 return code.toString(); }
上面的代碼中,總體邏輯仍是比較清晰的,讀者能夠根據上面的講解,而後結合前面展現的FruitGranter
生成的子類代碼進行對比來看,這樣就能夠對整個實現邏輯有比較好的理解了。上面的邏輯主要分爲了以下幾個步驟:
@Adaptive
註解,若是沒有標註,則爲其生成一個默認實現,該實現中會默認拋出異常,也就是說只有使用@Adaptive
註解標註的方法纔是做爲自適應機制的方法;@Adaptive
註解的配置獲取目標參數的key值,而後經過前面獲得的URL參數獲取該key對應的參數值,從而獲得了基礎服務類對應的名稱;ExtensionLoader
獲取該名稱對應的基礎服務類實例;能夠看到,這裏實現的自適應機制邏輯結構是很是清晰的,讀者經過閱讀這裏的源碼也就比較好的理解了Dubbo所提供的自適應機制的原理,也可以比較好的經過自適應機制來完成某些定製化的工做。
本文首先經過一個示例來說解了Dubbo的自適應機制的使用方式,而後在源碼的層面對自適應機制的實現原理進行了講解。