在 Dubbo 中,不少拓展都是經過 SPI 機制進行加載的,好比 Protocol、Cluster、LoadBalance 等,這些都是Dubbo的基礎組件。這些基礎組件的拓展不是在系統框架啓動階段被加載,而是拓展方法被調用時,根據運行時參數(URL)進行動態加載的。自適應拓展機制是創建在SPI基礎之上的,自適應拓展類的核心實現:在拓展接口的方法被調用時,經過SPI加載具體的拓展實現類,並調用拓展對象的同名方法。能夠看到自適應拓展是創建在SPI基礎上的實現的功能,自適應拓展加上SPI實現Dubbo可拓展和解耦,這是Dubbo很是重要機制。java
自適應拓展類的過程是:選根據接口方法上的"@Adaptive" 註解生成自適應代碼,而後經過Javassist編譯建立自適應拓展類代碼(使用系統內置特殊的自適應拓展類),再經過反射建立自適應拓展類的實例,而且保存至緩存。須要注意系統內置特殊的自適應拓展類是,分別是 AdaptiveCompiler 和 AdaptiveExtensionFactory,由於這兩個類的類被 Adaptive 註解了(是類被註解,而不是方法)。Dubbo這樣設計的緣由是:在產生自適應拓展類代碼時,爲了不死循環產生自適應拓展類代碼,這一點很重要,特別是新手很容易搞混淆。apache
這裏以"org.apache.dubbo.rpc.Protocol"做爲示例講解下建立自適應拓展類的邏輯,由於這部分功能邏輯單純從代碼上看很容易搞錯順序或關係,因此單獨拿出來解釋下。設計模式
代碼入口是"ExtensionLoader.getExtensionLoader"和SPI章節入口是同樣的,但咱們要了解的內容不同。下面是獲取ExtensionLoader實例的代碼在SPI章節講解過,這裏就不囉嗦了。緩存
@SuppressWarnings("unchecked") public static <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { logger.info("type:"+type); //空值判斷 if (type == null) { throw new IllegalArgumentException("Extension type == null"); } //判斷是否接口類 if (!type.isInterface()) { throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!"); } // 即:type.isAnnotationPresent(SPI.class),是否包含SPI註解 if (!withExtensionAnnotation(type)) { throw new IllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); } //new ExtensionLoader()時, //觸發ExtensionFactory的ExtensionLoader // 實例建立:ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); 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的私用構造方法還有其它代碼邏輯,也就是在初始化其它擴展類的ExtensionLoader實例以前,必須先初始化ExtensionFactory.class的ExtensionLoader實例。app
private ExtensionLoader(Class<?> type) { this.type = type; //先執行:ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension(),即獲得AdaptiveExtensionFactory //ExtensionFactory的objectFactory屬性爲空 //非ExtensionFactory的objectFactory屬性爲AdaptiveExtensionFactory objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); }
ExtensionFactory.class在獲取自適應拓展類實例時,由於ExtensionFactory.class的字類AdaptiveExtensionFactory有「@Adaptive」,因此AdaptiveExtensionFactory直接返回而且成爲自適應拓展類,這是特殊的例子。框架
在獲取自適應拓展類實例,先是從緩存獲取,若是緩存沒有命中,再建立。由於建立一個自適應拓展類實例太耗資源和時間了,因此這些對象都是以單例方式運行。dom
@SuppressWarnings("unchecked") public T getAdaptiveExtension() { logger.info("getAdaptiveExtension.type:"+type); // 從緩存中獲取自適應拓展 Object instance = cachedAdaptiveInstance.get(); // 緩存未命中 if (instance == null) { if (createAdaptiveInstanceError == null) { synchronized (cachedAdaptiveInstance) { instance = cachedAdaptiveInstance.get(); if (instance == null) { try { // 建立自適應拓展 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; }
這裏和SPI同樣,均使用了Dubbo的IOC,在注入依賴以前先獲取自適應類,並建立實例。ide
@SuppressWarnings("unchecked") private T createAdaptiveExtension() { try { //獲取自適應拓展類,而後建立實例,並經過反射注入該實例的依賴(Dubbo的IOC實現) return injectExtension((T) getAdaptiveExtensionClass().newInstance()); } catch (Exception e) { throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e); } }
這個方法包含三個邏輯性能
private Class<?> getAdaptiveExtensionClass() { // 獲取全部的拓展類 getExtensionClasses(); // 加載完全部的實現以後,若是發現有cachedAdaptiveClass不爲空,則直接返回緩存, // 同時也意味着自拓展適應類:cachedAdaptiveClass是AdaptiveExtensionFactory或AdaptiveCompiler if (cachedAdaptiveClass != null) { return cachedAdaptiveClass; } // 建立自適應拓展類 return cachedAdaptiveClass = createAdaptiveExtensionClass(); }
先獲取指定類(如:"org.apache.dubbo.rpc.Protocol")全部的拓展類,在獲取拓展類時,判斷拓展類是否標註了"@Adaptive",若是標註了"@Adaptive"表示當前類爲自適應拓展類,不須要經過動態生成代碼建立自適應拓展類。如:AdaptiveExtensionFactory和AdaptiveCompiler。ui
private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException {
//檢查clazz(擴展實現類)是否type(接口)的實現類
if (!type.isAssignableFrom(clazz)) {
throw new IllegalStateException("Error occurred when loading extension class (interface: " +
type + ", class line: " + clazz.getName() + "), class "
+ clazz.getName() + " is not subtype of interface.");
}
// 檢測目標類上是否有 Adaptive 註解,若是有則設置 cachedAdaptiveClass緩存
if (clazz.isAnnotationPresent(Adaptive.class)) {
cacheAdaptiveClass(clazz);
// 檢測 clazz 是不是 Wrapper 類型,若是有則存儲 clazz 到 cachedWrapperClasses 緩存中
} else if (isWrapperClass(clazz)) {
//判斷是不是wrapper類型(包裝類型),實現類的構造方法中的參數是擴展點類型的,就是一個Wrapper類
cacheWrapperClass(clazz);
} else {// 程序進入此分支,代表 clazz 是一個普通的拓展類
// 獲取默認的構造方法
clazz.getConstructor();
if (StringUtils.isEmpty(name)) {//name即Key值,如:spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory
// 若是 name 爲空,則嘗試從 Extension 註解中獲取 name,或使用小寫的類名做爲 name
name = findAnnotationName(clazz);
if (name.length() == 0) {
throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL);
}
}
String[] names = NAME_SEPARATOR.split(name);// 切分 name
if (ArrayUtils.isNotEmpty(names)) {
cacheActivateClass(clazz, names[0]);
for (String n : names) {
cacheName(clazz, n);// 存儲 Class 到名稱的映射關係
saveInExtensionClass(extensionClasses, clazz, n);//緩存Name與Clazz映謝關係
}
}
}
}
private void cacheAdaptiveClass(Class<?> clazz) { if (cachedAdaptiveClass == null) { // 設置 cachedAdaptiveClass緩存,並且這個類只有一個,也就是在指定的擴展類範圍只能有一個類能夠標註「@Adaptive」 cachedAdaptiveClass = clazz; } else if (!cachedAdaptiveClass.equals(clazz)) { throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName()); } }
若是全部拓展實現類都沒有合適標註"@Adaptive",那建立自適應拓展類的代碼。這個方法詳細解釋了爲何須要 AdaptiveCompiler 和 AdaptiveExtensionFactory的緣由。
private Class<?> createAdaptiveExtensionClass() { // 構建自適應拓展代碼 String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate(); // 獲取類加載器 ClassLoader classLoader = findClassLoader(); //dubbo默認使用javassist獲取編譯器 //若是沒有AdaptiveExtensionFactory和AdaptiveCompiler這兩個類,那cachedAdaptiveClass一直爲空,這個方法就會一直是死循環 //這也是Dubbo有會把@Adaptive標註類上面的緣由。 //解析Compiler的實現類的時候,會在getAdaptiveExtensionClass中直接返回AdaptiveCompiler,不須要動態生成代碼 org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader. getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); // 編譯代碼,生成 Class return compiler.compile(code, classLoader); }
產生自適應拓展類代碼的邏輯比較複雜,涉及到的細節也比較多,這裏以"org.apache.dubbo.rpc.Protocol"產生的適應拓展類代碼做爲例子,方便在閱讀源碼時做爲對比,方便理解。
package org.apache.dubbo.rpc; import org.apache.dubbo.common.extension.ExtensionLoader; public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol { public void destroy() { throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!"); } public int getDefaultPort() { throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!"); } public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException { if (arg1 == null) throw new IllegalArgumentException("url == null"); org.apache.dubbo.common.URL url = arg1; String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])"); org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); return extension.refer(arg0, arg1); } public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException { if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null"); if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null"); org.apache.dubbo.common.URL url = arg0.getUrl(); String extName = (url.getProtocol() == null ? "dubbo" : url.getProtocol()); if (extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])"); org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol) ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); return extension.export(arg0); } }
要產生自適應拓展類的接口至少有一個方法被標註了"@Adaptive",不然不產生代任何碼。由於在這個方法前面已經排除類的標註了,這裏只須要確認方法有註解就好了。
public String generate() { //Dubbo 要求該接口至少有一個方法被 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 org.apache.dubbo.rpc; code.append(generateImports());//生成import代碼,如import org.apache.dubbo.common.extension.ExtensionLoader; code.append(generateClassDeclaration());//生成聲明類代碼,如:public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol { // 經過反射獲取全部的方法 Method[] methods = type.getMethods(); // 遍歷方法列表 for (Method method : methods) { code.append(generateMethod(method)); } code.append("}"); if (logger.isDebugEnabled()) { logger.debug(code.toString()); } logger.info("\n\n code.toString:"+code.toString()); return code.toString(); }
產生代碼的重點在於方法,產生方法的時須要根據參數判斷和邏輯處理,相對複雜。
/** * generate method declaration * 生成"方法"代碼邏輯 */ private String generateMethod(Method method) { //返回參數類型,如:org.apache.dubbo.rpc.Exporter String methodReturnType = method.getReturnType().getCanonicalName(); //方法名代碼,如:export String methodName = method.getName(); //方法內容代碼 String methodContent = generateMethodContent(method); //方法參數代碼,如:org.apache.dubbo.rpc.Invoker arg0 String methodArgs = generateMethodArguments(method); //拋異常代碼,如:throws org.apache.dubbo.rpc.RpcException String methodThrows = generateMethodThrows(method); return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent); }
產生方法代碼時,首先產生是沒有標註 "@Adaptive"方法的邏輯,而後須要直接處理參數URL或間接處理URL參數,如:判斷是否爲空,判斷參數的屬性getURL方法等。若是全部參數都不直接包含URL或間接包含URL參數,就直接拋異常。URL是Dubbo在每一個層流轉過程當中,用於上傳下達傳遞參數的,是一個很重要的數據類型。
private String generateMethodContent(Method method) { //獲取方法上Adaptive註解 Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); StringBuilder code = new StringBuilder(512); //沒有標註 Adaptive 註解的方法生成代理邏輯:僅會生成一句拋出異常的代碼。 // throw new UnsupportedOperationException("The method if (adaptiveAnnotation == null) { return generateUnsupported(method); } else { // 遍歷參數列表,肯定 org.apache.dubbo.common.URL在方法參數位置 int urlTypeIndex = getUrlTypeIndex(method); // urlTypeIndex != -1,表示參數列表中存在 URL 參數 // found parameter in URL type if (urlTypeIndex != -1) { // 爲 URL 類型參數生成判空代碼,格式以下: // if (arg1 == null) throw new IllegalArgumentException("url == null"); code.append(generateUrlNullCheck(urlTypeIndex)); } else { // did not find parameter in URL type //若是參數不包括URL,那從參數的屬性應該包含URL,若是參數的屬性不包含URL屬性,那拋異常,如: //if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null"); // if (arg0.getUrl() == null) // throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null"); code.append(generateUrlAssignmentIndirectly(method)); } //獲取 Adaptive 註解值,若是不存在,採用類名,如:LoadBalance 通過處理後,獲得 load.balance String[] value = getMethodAdaptiveValue(adaptiveAnnotation); //檢測方法列表中是否存在 org.apache.dubbo.rpc.Invocation 類型的參數 boolean hasInvocation = hasInvocationArgument(method); // append代碼:爲Invocation 類型參數生成判空代碼 code.append(generateInvocationArgumentNullCheck(method)); //append代碼:生成拓展名獲取邏輯代碼 // String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() ); code.append(generateExtNameAssignment(value, hasInvocation)); // 生成 extName 判空代碼 //if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])"); code.append(generateExtNameNullCheck(value)); //生成拓展獲取代碼 //如:org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); code.append(generateExtensionAssignment()); //生成返回值代碼 //return extension.export(arg0); code.append(generateReturnAndInvocation(method)); } return code.toString(); }
經過反射方法獲取方法名稱、訪問權限、方法參數數量,而後判斷參數是否包括getUrl方法。
private String generateUrlAssignmentIndirectly(Method method) { Class<?>[] pts = method.getParameterTypes(); // 遍歷方法的參數類型列表 // find URL getter method for (int i = 0; i < pts.length; ++i) { // 獲取某一類型參數的所有方法 for (Method m : pts[i].getMethods()) { // 遍歷方法列表,尋找可返回 URL 的 getter 方法 String name = m.getName(); // 1. 方法名以 get 開頭,或方法名大於3個字符 // 2. 方法的訪問權限爲 public // 3. 非靜態方法 // 4. 方法參數數量爲0 // 5. 方法返回值類型爲 URL if ((name.startsWith("get") || name.length() > 3) && Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers()) && m.getParameterTypes().length == 0 && m.getReturnType() == URL.class) { return generateGetUrlNullCheck(i, pts[i], name); } } } // 若是全部參數中均不包含可返回 URL 的 getter 方法,則拋出異常 // getter method not found, throw throw new IllegalStateException("Failed to create adaptive class for interface " + type.getName() + ": not found url parameter or url attribute in parameters of method " + method.getName()); }
生成拓展名獲取邏輯代碼,這部分代碼比較複雜,判斷條件分支也多,須要屢次斷點而且對照已產生的代碼一塊兒閱讀比較容易理解。
private String generateExtNameAssignment(String[] value, boolean hasInvocation) { String getNameCode = null; // 遍歷 value,這裏的 value 是 Adaptive 的註解值。 // 此處循環目的是生成從 URL 中獲取拓展名的代碼,生成的代碼會賦值給 getNameCode 變量。注意這 // 個循環的遍歷順序是由後向前遍歷的。 for (int i = value.length - 1; i >= 0; --i) { if (i == value.length - 1) { // 默認拓展名非空 if (null != defaultExtName) { // protocol 是 url 的一部分,可經過 getProtocol 方法獲取,其餘的則是從 // URL 參數中獲取。由於獲取方式不一樣,因此這裏要判斷 value[i] 是否爲 protocol if (!"protocol".equals(value[i])) { // hasInvocation 用於標識方法參數列表中是否有 Invocation 類型參數 if (hasInvocation) { // 生成的代碼功能等價於下面的代碼: // url.getMethodParameter(methodName, value[i], defaultExtName) // 以 LoadBalance 接口的 select 方法爲例,最終生成的代碼以下: // url.getMethodParameter(methodName, "loadbalance", "random") getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName); } else { // 生成的代碼功能等價於下面的代碼: // url.getParameter(value[i], defaultExtName) getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName); } } else { // 生成的代碼功能等價於下面的代碼: // ( url.getProtocol() == null ? defaultExtName : url.getProtocol() ) getNameCode = String.format("( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName); } } else {// 默認拓展名爲空 if (!"protocol".equals(value[i])) { if (hasInvocation) { // 生成代碼格式同上 getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName); } else { // 生成的代碼功能等價於下面的代碼: // url.getParameter(value[i]) getNameCode = String.format("url.getParameter(\"%s\")", value[i]); } } else { // 生成從 url 中獲取協議的代碼,好比 "dubbo" getNameCode = "url.getProtocol()"; } } } else { if (!"protocol".equals(value[i])) { if (hasInvocation) { // 生成代碼格式同上 getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName); } else { // 生成的代碼功能等價於下面的代碼: // url.getParameter(value[i], getNameCode) // 以 Transporter 接口的 connect 方法爲例,最終生成的代碼以下: // url.getParameter("client", url.getParameter("transporter", "netty")) getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode); } } else { // 生成的代碼功能等價於下面的代碼: // url.getProtocol() == null ? getNameCode : url.getProtocol() // 以 Protocol 接口的 connect 方法爲例,最終生成的代碼以下: // url.getProtocol() == null ? "dubbo" : url.getProtocol() getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode); } } } // 生成 extName 賦值代碼 return String.format(CODE_EXT_NAME_ASSIGNMENT, getNameCode); }
獲得一個完整的自適應拓展類後,自適應拓展類的核心邏輯:在拓展接口的方法被調用時,基於SPI機制而且根據URL或間接的URL參數加載具體的拓展實現類,並調用拓展對象的同名方法。
最後獲得的是String類型自適應拓展類代碼,根據String代碼建立Class對象和實現,這時候就是輪到Javassit出場了。
@Adaptive public class AdaptiveCompiler implements Compiler { private static volatile String DEFAULT_COMPILER; public static void setDefaultCompiler(String compiler) { DEFAULT_COMPILER = compiler; } @Override public Class<?> compile(String code, ClassLoader classLoader) { Compiler compiler; //獲得一個ExtensionLoader ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class); //默認的Compiler名字 String name = DEFAULT_COMPILER; // copy reference if (name != null && name.length() > 0) { compiler = loader.getExtension(name); } else { //若是沒有設置編譯器的擴展名,就使用默認編譯器 //默認值:org.apache.dubbo.common.compiler.support.JavassistCompiler compiler = loader.getDefaultExtension(); } //擴展實現類來編譯代碼 return compiler.compile(code, classLoader); } }
本章節涉及的知識點有字節碼技術,Java主要流行的字節碼操縱框架有Javassist 和 ASM。字節碼技術主要是Dubbo默認是使用Javassist字節碼,Dubbo使用Javassist是基於性能和易用性兩方面考慮的。具體能夠參考早期 dubbo 的做者梁飛的博客 http://javatar.iteye.com/blog/814426,從這裏面也能夠知道技術選型的過程和考量點。
本章節涉及的知識點還有設計模式-代理模式,代理模式在Dubbo中是普遍應用的,Dubbo並無直接使用JDK的代理技術,而是經過Javassist實現代理。代理模式在Dubbo、Spring都是普遍應用, 特別是Dubbo的服務導入與導出部分大量使用。
本章節涉及到自適應拓展類機制,是Dubbo的重要的基礎機制。基於Dubbo的SPI的自適應拓展機制能夠動態調用拓展實現類,不少基礎組件都依賴些機制Protocol、Cluster、LoadBalance 等。