本篇介紹自適應擴展,方法getAdaptiveExtension()的實現。ExtensionLoader類自己不少功能也使用到了自適應擴展。包括ExtensionFactory擴展。java
通俗的講,自適應擴展要實現的邏輯是:調用擴展點的方法時,自動判斷要調用那個擴展點實現類的方法。咱們知道,一個擴展點一般有多個實現類,在配置文本文件中分多行配置,在前面的分析中,咱們知道經過getExtension(String name)方法,返回的是指定key的擴展點,而自適應擴展點方法getAdaptiveExtension()在調用前,不確認返回那個擴展點。而是在方法調用的時候,根據方法入參,進行肯定,具體是調用那個實現類。數組
自適應擴展基於@Adaptive註解,能夠修飾類,也能夠修飾方法。修飾類的時候,邏輯比較簡單,不會動態生成代碼邏輯,使用的場景也比較少,主要包括AdaptiveCompiler 和 AdaptiveExtensionFactory。修飾方法的時候,會動態生成一個新類,新類包括擴展點的全部方法,調用getAdaptiveExtension()返回的就是新類對象。緩存
前面咱們說過,@Adaptive能夠修飾類,也能夠修飾方法。咱們先看下修飾類的場景。app
經過一個具體的實現類來看下,這裏咱們分析AdaptiveCompiler類的實現:dom
1 @Adaptive 2 public class AdaptiveCompiler implements Compiler { 3 4 private static volatile String DEFAULT_COMPILER; 5 6 public static void setDefaultCompiler(String compiler) { 7 DEFAULT_COMPILER = compiler; 8 } 9 10 @Override 11 public Class<?> compile(String code, ClassLoader classLoader) { 12 Compiler compiler; 13 ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class); 14 String name = DEFAULT_COMPILER; // copy reference 15 if (name != null && name.length() > 0) { 16 compiler = loader.getExtension(name); 17 } else { 18 compiler = loader.getDefaultExtension(); 19 } 20 return compiler.compile(code, classLoader); 21 } 22 23 }
此類,只有一個對外提供的方法compile(String code, ClassLoader classLoader),咱們來看方法的實現。編輯器
首先溝通ExtensionLoader.getExtensionLoader(Compiler.class),獲取Compiler對應的ExtensionLoader對象,而後判斷構造器中初始化的DEFAULT_COMPILER 變量是否有值。若是存在就經過loader.getExtension(name)方法得到擴展點實現。若是DEFAULT_COMPILER 爲空,則調用loader.getDefaultExtension()方法,返回默認實現。獲取compiler擴展點實現對象後,調用對應的compile方法。ide
由此,咱們能夠看到,@Adaptive修飾的類,在調用具體方法的時候,是根據必定的條件進行判斷,確認具體調用的實現類對象。函數
咱們再說下@Adaptive修飾方法的場景。ui
擴展點實現類的方法若是被@Adaptive修飾,在調用getAdaptiveExtension()方法時候,程序會自動生成一個新類,新類是一個名爲擴展點接口名+$Adaptive,實現了擴展點接口的類。新類中的方法,主要分爲兩類,一是有@Adaptive註解的方法,一個是沒有@Adaptive註解的方法。url
有@Adaptive註解的方法,方法內部會判斷方法入參是否有URL(此處是dubbo內的URL),或是方法入參對象是否能夠get到URL。若是都不能獲取到URL,直接throw 出Exception。若是能獲取到URL,則從URL對象中獲取須要調用的實現類對應的配置文本文件的key,根據什麼參數從URL中獲取呢?首先是從@Adaptive的value獲取(此value是一個字符串數組),若是@Adaptive爲空,則根據類名進行轉換,得出從URL獲取key的參數名,轉換規則是根據駝峯規則,遇到大寫字符添加」.「,如 AdaptiveFactory 爲:adaptive.factory。得到參數後,再經過getExtension(..)方法,得到須要調用的擴展點實現類對象。
到這裏,咱們基本介紹了自適應擴展點的實現邏輯,可是有一點沒有說到,就是無論@Adaptive修飾類仍是修飾方法,自適應擴展點的返回邏輯,這點是要結合代碼進行說明,接下來就開啓咱們的源代碼分析。
咱們從getAdaptiveExtension()方法開始
1 public T getAdaptiveExtension() { 2 // 從緩存中獲取自定義拓展 3 Object instance = cachedAdaptiveInstance.get(); 4 if (instance == null) { 5 if (createAdaptiveInstanceError == null) { 6 synchronized (cachedAdaptiveInstance) { 7 instance = cachedAdaptiveInstance.get(); 8 if (instance == null) { 9 try { 10 instance = createAdaptiveExtension(); 11 cachedAdaptiveInstance.set(instance); 12 } catch (Throwable t) { 13 createAdaptiveInstanceError = t; 14 throw new IllegalStateException("fail to create adaptive instance: " + t.toString(), t); 15 } 16 } 17 } 18 } else { 19 throw new IllegalStateException("fail to create adaptive instance: " + createAdaptiveInstanceError.toString(), createAdaptiveInstanceError); 20 } 21 } 22 23 return (T) instance; 24 }
這個方法的邏輯很簡單,主要包括
一、從緩存對象cachedAdaptiveInstance獲取自適應擴展點實例
二、緩存有直接返回,沒有進行方法createAdaptiveExtension()調用
三、根據方法返回的實例,設置到到緩存裏,並進行返回
因此咱們接着分析createAdaptiveExtension方法
1 private T createAdaptiveExtension() { 2 try { 3 // injectExtension 爲@Adaptive註解的類 可能存在的IOC服務 4 // @Adaptive註解方法 自動生成的代理類不存在IOC可能 5 T instance = (T) getAdaptiveExtensionClass().newInstance(); 6 return injectExtension(instance); 7 } catch (Exception e) { 8 throw new IllegalStateException("Can not create adaptive extension " + type + ", cause: " + e.getMessage(), e); 9 } 10 }
能夠看到,方法內部是經過getAdaptiveExtensionClass() 獲取到Class實例,再反射實例化,獲取到實例對象。
咱們接着往下看getAdaptiveExtensionClass()方法
1 private Class<?> getAdaptiveExtensionClass() { 2 // 經過SPI獲取全部的擴展類,賦值相關的成員變量 3 getExtensionClasses(); 4 // 若是有@Adaptive修飾的類,cachedAdaptiveClass不爲空 5 if (cachedAdaptiveClass != null) { 6 return cachedAdaptiveClass; 7 } 8 // 沒有@Adaptive修飾的類時,根據@Adaptive修飾方法 建立自適應擴展類 9 return cachedAdaptiveClass = createAdaptiveExtensionClass(); 10 }
首先執行的是getExtensionClasses()方法,以後判斷cachedAdaptiveClass 是否爲空,不爲空就直接返回了。這個cachedAdaptiveClass 變量,其實就是有@Adaptive修飾的擴展點實現。也就是說,若是在擴展點的實現類中,存在@Adaptive修飾的類,就直接返回這個類了。
那麼cachedAdaptiveClass 在是哪裏賦值的呢?咱們須要再看getExtensionClasses()方法。getExtensionClasses這個方法在前面兩篇文章中已經都有介紹。在默認擴展點的實現裏面,cachedDefaultName變量的賦值就是在這個方法裏進行的。cachedAdaptiveClass 的賦值的方法調用鏈咱們這裏直接給出來
1 getExtensionClasses()-->loadExtensionClasses()-->loadDirectory()-->loadResource()-->loadClass()
隱藏的比較深,第5個方法纔對cachedDefaultName進行了賦值。
咱們一步一步來分析,先看getExtensionClasses()
1 private Map<String, Class<?>> getExtensionClasses() { 2 Map<String, Class<?>> classes = cachedClasses.get(); 3 if (classes == null) { 4 synchronized (cachedClasses) { 5 classes = cachedClasses.get(); 6 if (classes == null) { 7 classes = loadExtensionClasses(); 8 cachedClasses.set(classes); 9 } 10 } 11 } 12 return classes; 13 }
這個方法很簡單,咱們接着看loadExtensionClasses()
1 private Map<String, Class<?>> loadExtensionClasses() { 2 // 獲取註解 SPI的接口 3 // type爲傳入的擴展接口,必須有@SPI註解 4 final SPI defaultAnnotation = type.getAnnotation(SPI.class); 5 // 獲取默認擴展實現value,若是存在,賦值給cachedDefaultName 6 if (defaultAnnotation != null) { 7 String value = defaultAnnotation.value(); 8 if ((value = value.trim()).length() > 0) { 9 // @SPI value 只能是一個,不能爲逗號分割的多個 10 // @SPI value爲默認的擴展實現 11 String[] names = NAME_SEPARATOR.split(value); 12 if (names.length > 1) { 13 throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() + ": " + Arrays.toString(names)); 14 } 15 if (names.length == 1) 16 cachedDefaultName = names[0]; 17 } 18 } 19 // 加載三個目錄配置的擴展類 20 Map<String, Class<?>> extensionClasses = new HashMap<String, Class<?>>(); 21 // META-INF/dubbo/internal 22 loadDirectory(extensionClasses, DUBBO_INTERNAL_DIRECTORY); 23 // META-INF/dubbo 24 loadDirectory(extensionClasses, DUBBO_DIRECTORY); 25 // META-INF/services/ 26 loadDirectory(extensionClasses, SERVICES_DIRECTORY); 27 return extensionClasses; 28 }
很熟悉吧,這個方法咱們在第一篇文章中已經有介紹了,咱們再接着往下看loadDirectory方法
1 private void loadDirectory(Map<String, Class<?>> extensionClasses, String dir) { 2 // 擴展配置文件完整文件路徑+文件名 3 String fileName = dir + type.getName(); 4 try { 5 6 Enumeration<java.net.URL> urls; 7 // 獲取類加載器 8 ClassLoader classLoader = findClassLoader(); 9 if (classLoader != null) { 10 urls = classLoader.getResources(fileName); 11 } else { 12 urls = ClassLoader.getSystemResources(fileName); 13 } 14 if (urls != null) { 15 while (urls.hasMoreElements()) { 16 java.net.URL resourceURL = urls.nextElement(); 17 // 加載 18 loadResource(extensionClasses, classLoader, resourceURL); 19 } 20 } 21 } catch (Throwable t) { 22 logger.error("Exception when load extension class(interface: " + type + ", description file: " + fileName + ").", t); 23 } 24 }
這個方法咱們也很分析過了,再往下看loadResource()方法
1 private void loadResource(Map<String, Class<?>> extensionClasses, ClassLoader classLoader, java.net.URL resourceURL) { 2 try { 3 BufferedReader reader = new BufferedReader(new InputStreamReader(resourceURL.openStream(), "utf-8")); 4 try { 5 String line; 6 while ((line = reader.readLine()) != null) { 7 // 字符#是註釋開始標誌,只取#前面的字符 8 final int ci = line.indexOf('#'); 9 if (ci >= 0) 10 line = line.substring(0, ci); 11 line = line.trim(); 12 if (line.length() > 0) { 13 try { 14 String name = null; 15 int i = line.indexOf('='); 16 if (i > 0) { 17 // 解析出 name 和 實現類 18 name = line.substring(0, i).trim(); 19 line = line.substring(i + 1).trim(); 20 } 21 if (line.length() > 0) { 22 loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name); 23 } 24 } catch (Throwable t) { 25 IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + resourceURL + ", cause: " + t.getMessage(), t); 26 exceptions.put(line, e); 27 } 28 } 29 } 30 } finally { 31 reader.close(); 32 } 33 } catch (Throwable t) { 34 logger.error("Exception when load extension class(interface: " + type + ", class file: " + resourceURL + ") in " + resourceURL, t); 35 } 36 }
這裏是解析配置文本文件的內容,經過反射得到Class,再調用loadClass(),咱們接着往下
1 private void loadClass(Map<String, Class<?>> extensionClasses, java.net.URL resourceURL, Class<?> clazz, String name) throws NoSuchMethodException { 2 // type是否爲clazz的超類,clazz是否實現了type接口 3 // 此處clazz 是擴展實現類的Class 4 if (!type.isAssignableFrom(clazz)) { 5 throw new IllegalStateException("Error when load extension class(interface: " + type + ", class line: " + clazz.getName() + "), class " + clazz.getName() + "is not subtype of interface."); 6 } 7 // clazz是否註解了 Adaptive 自適應擴展 8 // 不容許多個類註解Adaptive 9 // 註解adaptive的實現類,賦值給cachedAdaptiveClass 10 if (clazz.isAnnotationPresent(Adaptive.class)) { 11 if (cachedAdaptiveClass == null) { 12 cachedAdaptiveClass = clazz; 13 // 不容許多個實現類都註解@Adaptive 14 } else if (!cachedAdaptiveClass.equals(clazz)) { 15 throw new IllegalStateException("More than 1 adaptive class found: " + cachedAdaptiveClass.getClass().getName() + ", " + clazz.getClass().getName()); 16 } 17 // 是否爲包裝類,判斷擴展類是否提供了參數是擴展點的構造函數 18 } else if (isWrapperClass(clazz)) { 19 Set<Class<?>> wrappers = cachedWrapperClasses; 20 if (wrappers == null) { 21 cachedWrapperClasses = new ConcurrentHashSet<Class<?>>(); 22 wrappers = cachedWrapperClasses; 23 } 24 wrappers.add(clazz); 25 // 普通擴展類 26 } else { 27 // 檢測 clazz 是否有默認的構造方法,若是沒有,則拋出異常 28 clazz.getConstructor(); 29 // 此處name爲 SPI配置中的key 30 // @SPI配置中key能夠爲空,此時key爲擴展類的類名(getSimpleName())小寫 31 if (name == null || name.length() == 0) { 32 // 兼容舊版本 33 name = findAnnotationName(clazz); 34 if (name.length() == 0) { 35 throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL); 36 } 37 } 38 // 逗號分割 39 String[] names = NAME_SEPARATOR.split(name); 40 if (names != null && names.length > 0) { 41 // 獲取Activate註解 42 Activate activate = clazz.getAnnotation(Activate.class); 43 if (activate != null) { 44 cachedActivates.put(names[0], activate); 45 } 46 for (String n : names) { 47 if (!cachedNames.containsKey(clazz)) { 48 cachedNames.put(clazz, n); 49 } 50 // name不能重複 51 Class<?> c = extensionClasses.get(n); 52 if (c == null) { 53 extensionClasses.put(n, clazz); 54 } else if (c != clazz) { 55 throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName()); 56 } 57 } 58 } 59 } 60 }
咱們在第10行,終於看到了Adaptive註解判斷。
若是擴展點實現類存在@Adaptive註解,Class對象賦值給cachedAdaptiveClass,而且在第14行判斷是否存在多個類都是@Adaptive註解,若是同一個擴展點的多個實現類都有@Adaptive註解,則拋出異常。
到這裏,咱們看到了擴展點自適應擴展點的類級別註解的調用及返回邏輯。其實前面也說過了,@Adaptive修飾類的場景並很少,也不是重點,重點是@Adaptive修飾方法的時候。
咱們返回到getAdaptiveExtensionClass()方法,爲了清晰,咱們再看看這個方法的代碼
1 private Class<?> getAdaptiveExtensionClass() { 2 // 經過SPI獲取全部的擴展類,賦值相關的成員變量 3 getExtensionClasses(); 4 // 若是有@Adaptive修飾的類,cachedAdaptiveClass不爲空 5 if (cachedAdaptiveClass != null) { 6 return cachedAdaptiveClass; 7 } 8 // 沒有@Adaptive修飾的類時,根據@Adaptive修飾方法 建立自適應擴展類 9 return cachedAdaptiveClass = createAdaptiveExtensionClass(); 10 }
經過前面的分析,若是@Adaptive沒有修飾類,則cachedAdaptiveClass 爲空,此時,咱們會進入createAdaptiveExtensionClass(),這個方法是實現@Adaptive修飾方法的邏輯實現,也是自適應擴展的重點所在。
咱們來看createAdaptiveExtensionClass這個方法
1 private Class<?> createAdaptiveExtensionClass() { 2 // 建立自適應擴展代碼 字符串 3 String code = createAdaptiveExtensionClassCode(); 4 ClassLoader classLoader = findClassLoader(); 5 // 獲取編譯器實現類 6 com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); 7 // 編譯代碼,獲取自適應擴展類的Class 8 return compiler.compile(code, classLoader); 9 }
這個方法實現的功能主要兩點:
一、經過createAdaptiveExtensionClassCode()獲取建立的新類字符串
二、經過Compiler編譯器,編譯新類字符串,得到新類的Class對象
因此,咱們的重點是分析新類字符串的實現邏輯,這也是自適應擴展的重點。
咱們接着看createAdaptiveExtensionClassCode()方法,這個方法有300多行,咱們分段來看
1 private String createAdaptiveExtensionClassCode() { 2 StringBuilder codeBuilder = new StringBuilder(); 3 // 經過反射獲取全部方法 4 Method[] methods = type.getMethods(); 5 boolean hasAdaptiveAnnotation = false; 6 // 遍歷方法,判斷至少有一個方法被@Adaptive修飾 7 for (Method m : methods) { 8 if (m.isAnnotationPresent(Adaptive.class)) { 9 hasAdaptiveAnnotation = true; 10 break; 11 } 12 } 13 // no need to generate adaptive class since there's no adaptive method found. 14 // 沒有被@Adaptive修飾的方法,拋出異常 15 if (!hasAdaptiveAnnotation) 16 throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!"); 17 // 生成package代碼:package+type所在包 18 codeBuilder.append("package ").append(type.getPackage().getName()).append(";"); 19 // 生成import代碼:import+ExtensionLoader權限定名 20 codeBuilder.append("\nimport ").append(ExtensionLoader.class.getName()).append(";"); 21 // 生成類代碼:public class + type簡單名稱+$Adaptive+implements + type權限定名+{ 22 codeBuilder.append("\npublic class ").append(type.getSimpleName()).append("$Adaptive").append(" implements ").append(type.getCanonicalName()).append(" {"); 23 ............... 24 ...暫時省略.... 25 .............. 26 }
方法開始的邏輯很簡單,拿到擴展點type的方法,循環方法列表,判斷是否存在一個方法是被@Adaptive註解的,若是存在則繼續,不然拋出異常。這也符合正常的邏輯,若是全部的方法都沒@Adaptive註解,那麼獲取自適應擴展就沒有意義了。
從15行開始進行新類字符串的構造,咱們看到了關鍵字」package「、」import「、」class「等,執行到22行處,生成的代碼字符串,咱們以擴展點Protocol爲例,展現一下:
1 package com.alibaba.dubbo.rpc; 2 import com.alibaba.dubbo.common.extension.ExtensionLoader; 3 public class Protocol$Adaptive implements com.alibaba.dubbo.rpc.Protocol { 4 // 省略方法代碼 5 }
這個類就是咱們生成的新的擴展點實現類,咱們能夠看到類名以及實現的接口。
咱們接着往下分析,前面咱們說過,擴展點方法分爲兩種,一個是有@Adaptive註解的,一個是無@Adaptive註解的,既然實現了擴展點接口,這兩種方法都要在新類中實現。
咱們首先分析沒有@Adaptive註解的方法。
1 private String createAdaptiveExtensionClassCode() { 2 ............... 3 ...暫時省略.... 4 .............. 5 // 生成方法 6 for (Method method : methods) { 7 // 方法返回類型 8 Class<?> rt = method.getReturnType(); 9 // 方法參數數組 10 Class<?>[] pts = method.getParameterTypes(); 11 // 方法異常數組 12 Class<?>[] ets = method.getExceptionTypes(); 13 // 方法的Adaptive註解 14 Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class); 15 StringBuilder code = new StringBuilder(512); 16 // 沒有@Adaptive註解的方法,生成的方法體內爲 throw 異常 17 if (adaptiveAnnotation == null) { 18 // throw new UnsupportedOperationException( 19 // "method " + 方法簽名 + of interface + 全限定接口名 + is not adaptive method!」) 20 code.append("throw new UnsupportedOperationException(\"method ").append(method.toString()).append(" of interface ").append(type.getName()).append(" is not adaptive method!\");"); 21 } else { 22 ............... 23 ...暫時省略.... 24 .............. 25 }
經過for循環,進行逐個方法生成。
在第14行,獲取到方法的@Adaptive註解,第17行進行判斷,若是爲空,生成的代碼是拋出一個異常,示例以下:
1 throw new UnsupportedOperationException( 2 "method public abstract void com.alibaba.dubbo.rpc.Protocol.destroy() of interface com.alibaba.dubbo.rpc.Protocol is not adaptive method!");
咱們接着分析存在@Adaptive註解的方法,生成代碼的邏輯
1 ...暫時省略.... 2 if (adaptiveAnnotation == null) { 3 // throw new UnsupportedOperationException( 4 // "method " + 方法簽名 + of interface + 全限定接口名 + is not adaptive method!」) 5 code.append("throw new UnsupportedOperationException(\"method ").append(method.toString()) 6 .append(" of interface ").append(type.getName()).append(" is not adaptive method!\");"); 7 } else { 8 // 有@Adaptive註解的方法,參數中必須有URL,或是能夠從方法參數中獲取URL 9 int urlTypeIndex = -1; 10 for (int i = 0; i < pts.length; ++i) { 11 if (pts[i].equals(URL.class)) { 12 urlTypeIndex = i; 13 break; 14 } 15 } 16 // found parameter in URL type 17 // 方法中存在URL 18 if (urlTypeIndex != -1) { 19 // Null Point check 20 // 爲URL類型參數判斷空代碼,格式以下: 21 // if (arg + urlTypeIndex == null) 22 // throw new IllegalArgumentException("url == null"); 23 String s = String.format( 24 "\nif (arg%d == null) throw new IllegalArgumentException(\"url == null\");", urlTypeIndex); 25 code.append(s); 26 // 爲URL類型參數生成賦值代碼,例如:URL url = arg1; 27 s = String.format("\n%s url = arg%d;", URL.class.getName(), urlTypeIndex); 28 code.append(s); 29 } 30 ...暫時省略....
第10行,循環的變量pts 爲方法參數數組,若是參數數組中有URL類型,數組下標賦值給urlTypeIndex,跳出循環。
第18行進行urlTypeIndex 判斷,此時若是不爲-1,說明方法參數數組中存在URL類型的參數,生成的代碼首先是非空判斷,接着就是把對應的URL類型參數就行變量賦值。
接着往下看
1 ...暫時省略.... 2 else { 3 String attribMethod = null; 4 // find URL getter method 5 // 遍歷方法的參數類型 6 LBL_PTS: for (int i = 0; i < pts.length; ++i) { 7 // 方法參數類型的方法數組 8 Method[] ms = pts[i].getMethods(); 9 for (Method m : ms) { 10 String name = m.getName(); 11 // 一、方法名以get開頭,或方法名大於3個字符 12 // 二、方法的訪問權限是public 13 // 三、非靜態方法 14 // 四、方法參數數量爲0 15 // 五、方法返回值類型爲URL 16 if ((name.startsWith("get") || name.length() > 3) && Modifier.isPublic(m.getModifiers()) 17 && !Modifier.isStatic(m.getModifiers()) && m.getParameterTypes().length == 0 18 && m.getReturnType() == URL.class) { 19 urlTypeIndex = i; 20 attribMethod = name; 21 // 結束for (int i = 0; i < pts.length; ++i)循環 22 break LBL_PTS; 23 } 24 } 25 } 26 ...暫時省略....
上面的代碼是參數數組中不存在URL類型參數的狀況。若是不存在URL類型的參數,就須要從全部的入參中判斷,參數對象中是否能夠經過get方法 獲取到URL對象。若是不能夠則拋出異常。
標籤LBL_PTS用於結束最外部的循環。咱們看到最外邊的循環,仍是參數數組pts。
第8行是拿到參數數組中單個參數的全部方法,再進行循環,判斷是否存在知足以下條件: 一、方法名以get開頭,或方法名大於3個字符;二、方法的訪問權限是public; 三、非靜態方法;四、方法參數數量爲0; 五、方法返回值類型爲URL的方法,若是存在賦值方法名給attribMethod ,跳出最外變循環。
咱們接着往下看
1 ...暫時省略.... 2 // 若是參數中都不包含可返回的URL的get方法,拋出異常 3 if (attribMethod == null) { 4 throw new IllegalStateException("fail to create adaptive class for interface " + type.getName() 5 + ": not found url parameter or url attribute in parameters of method " 6 + method.getName()); 7 } 8 ...暫時省略....
咱們看到,若是attribMethod 爲空,也就是前面的兩個循環沒有找到存在返回URL方法的參數對象,直接拋出異常,方法結束執行。
若是attribMethod 不爲空,即存在返回URL方法的參數對象,咱們再往下看:
1 ...暫時省略.... 2 // Null point check 3 // 爲可返回URL的參數生成判空代碼,格式以下: 4 // if (arg + urlTypeIndex == null) 5 // throw new IllegalArgumentException("參數全限定名 + argument == null"); 6 String s = String.format( 7 "\nif (arg%d == null) throw new IllegalArgumentException(\"%s argument == null\");", 8 urlTypeIndex, pts[urlTypeIndex].getName()); 9 code.append(s); 10 // 爲 getter 方法返回的 URL 生成判空代碼,格式以下: 11 // if (argN.getter方法名() == null) 12 // throw new IllegalArgumentException(參數全限定名 + argument getUrl() == null); 13 s = String.format( 14 "\nif (arg%d.%s() == null) throw new IllegalArgumentException(\"%s argument %s() == null\");", 15 urlTypeIndex, attribMethod, pts[urlTypeIndex].getName(), attribMethod); 16 code.append(s); 17 // 生成賦值語句,格式以下: 18 // URL全限定名 url = argN.getter方法名(),好比 19 // com.alibaba.dubbo.common.URL url = invoker.getUrl(); 20 s = String.format("%s url = arg%d.%s();", URL.class.getName(), urlTypeIndex, attribMethod); 21 code.append(s); 22 ...暫時省略....
第6行生成新類代碼是空判斷,若是參數數組下標爲urlTypeIndex的參數爲空,拋出異常。
第13行是返回URL類型方法的空判斷,咱們知道參數數組下標是urlTypeIndex的參數,存在返回URL類型的方法,方法名爲attribMethod,此處就是判斷方法attribMethod是否爲空null。
第20行就是執行attribMethod,賦值給url變量。
至此,從入參中得到了URL類型的變量。前面是直接從參數數組中獲取類型爲URL的參數,後面是從參數數組中的某個能夠返回URL參數方法的參數。兩種方式目的就是獲取到URL類型的變量,這個是必須的,由於自適應的擴展,獲取擴展點的key是從URL中解析出來的。
在獲取到URL類型的變量後,如今就要獲取關鍵字key了,根據key從URL中獲取的value,就是自適應擴展點在配置文本文件中對應的key。
咱們接着往下看
1 ...暫時省略.... 2 // 獲取方法@Adaptive的註解值 3 String[] value = adaptiveAnnotation.value(); 4 // value is not set, use the value generated from class name as the key 5 // @Adaptive的value爲空,須要特殊處理 6 // 將類名轉換爲字符數組,而後遍歷字符數組,並將字符存入StringBulder 7 // 若字符爲大寫字母,則向StringBuiilder中添加「.」,隨後字符變爲小寫存入StringBuilder 8 // 好比LoadBalance通過處理,獲得load.balance 9 if (value.length == 0) { 10 // 獲取類名,並將類名轉換爲字符數組 11 char[] charArray = type.getSimpleName().toCharArray(); 12 StringBuilder sb = new StringBuilder(128); 13 // 遍歷字符數組 14 for (int i = 0; i < charArray.length; i++) { 15 // 判斷大小寫 16 if (Character.isUpperCase(charArray[i])) { 17 if (i != 0) { 18 // 大寫字符時,加 19 sb.append("."); 20 } 21 // 轉爲小寫 22 sb.append(Character.toLowerCase(charArray[i])); 23 } else { 24 sb.append(charArray[i]); 25 } 26 } 27 value = new String[] { sb.toString() }; 28 } 29 ...暫時省略....
第3行,是直接從@Adaptive註解中獲取value,類型爲字符串數組。
若是@Adaptive註解沒有設置value的值,接着看第9行的判斷。
從第11行開始,自動生成從URL獲取自適應擴展關鍵字的key。生成的邏輯是根據擴展點type的名稱,遍歷type名稱的字符數組,除了首字符,遇到大寫的字符前面加「.",大寫轉小寫,組裝的字符串就是要獲取的value值。
咱們接着往下看
1 ...暫時省略.... 2 // 檢測方法列表中是否存在Invocation類型的參數 3 // 若存在,則爲其生成判空代碼和其餘一些代碼 4 boolean hasInvocation = false; 5 for (int i = 0; i < pts.length; ++i) { 6 // 判斷參數名稱是否等於 com.alibaba.dubbo.rpc.Invocation 7 if (pts[i].getName().equals("com.alibaba.dubbo.rpc.Invocation")) { 8 // Null Point check 9 // 爲Invocation 類型參數生成判空代碼 10 String s = String.format( 11 "\nif (arg%d == null) throw new IllegalArgumentException(\"invocation == null\");", i); 12 code.append(s); 13 // 生成getMethodName方法調用代碼,格式爲: 14 // String methodName = argN.getMethodName(); 15 s = String.format("\nString methodName = arg%d.getMethodName();", i); 16 code.append(s); 17 // 設置hasInvocation爲true 18 hasInvocation = true; 19 break; 20 } 21 } 22 ...暫時省略....
這段代碼選好pts,判斷是否存在類型爲Invocation的參數。若是存在生成爲空判斷,以後從Invocation類型的參數中獲取methodName。並設置hasInvocation爲true。
1 ...暫時省略.... 2 /** 3 * 根據SPI和Adaptive註解值生成「獲取擴展名邏輯」,同時生成邏輯也受Invocation類型參數影響 生成格式如: String extName = 4 * url.getMethodParameter(methodName, "loadbalance","random"); 5 */ 6 // 設置默認擴展名,cachedDefaultName源於SPI註解值,默認狀況下, 7 // SPI註解值爲空串,此時cachedDefaultName 爲 null 8 String defaultExtName = cachedDefaultName; 9 String getNameCode = null; 10 // 遍歷value,value是Adaptive的註解值,上面有value的獲取過程 11 // 此處循環的目的是生成從URL中獲取擴展名的代碼,生成的代碼會賦值給getNameCode變量 12 // 這個循環的遍歷順序是由後向前遍歷的 13 for (int i = value.length - 1; i >= 0; --i) { 14 // i爲最後一個元素的座標時 15 if (i == value.length - 1) { 16 // 默認擴展名非空 17 if (null != defaultExtName) { 18 // protocol是擴展名的一部分,能夠經過getProtocol方法獲取,其餘則是從URL參數中獲取 19 // 由於獲取方式不一樣,所以要進行判斷 20 if (!"protocol".equals(value[i])) { 21 // hasInvocation 用於標識方法參數列表中是否有Invocation類型參數 22 if (hasInvocation) { 23 // 生成的代碼功能等價於下面的代碼: 24 // url.getMethodParameter(methodName, value[i], defaultExtName) 25 // 以 LoadBalance 接口的 select 方法爲例,最終生成的代碼以下: 26 // url.getMethodParameter(methodName, "loadbalance", "random") 27 getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", 28 value[i], defaultExtName); 29 } else { 30 // 生成的代碼功能等價於下面的代碼: 31 // url.getParameter(value[i], defaultExtName) 32 getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], 33 defaultExtName); 34 } 35 } else { 36 // 生成的代碼功能等價於下面的代碼: 37 // ( url.getProtocol() == null ? defaultExtName : url.getProtocol() ) 38 getNameCode = String.format( 39 "( url.getProtocol() == null ? \"%s\" : url.getProtocol() )", defaultExtName); 40 // 默認擴展名爲空 41 } 42 } else { 43 if (!"protocol".equals(value[i])) { 44 if (hasInvocation) { 45 // 生成代碼格式同上 46 getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", 47 value[i], defaultExtName); 48 } else { 49 // 生成的代碼功能等價於下面的代碼: 50 // url.getParameter(value[i]) 51 getNameCode = String.format("url.getParameter(\"%s\")", value[i]); 52 } 53 } else { 54 // 生成從 url 中獲取協議的代碼,好比 "dubbo" 55 getNameCode = "url.getProtocol()"; 56 } 57 } 58 } else { 59 if (!"protocol".equals(value[i])) { 60 if (hasInvocation) { 61 // 生成代碼格式同上 62 getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", 63 value[i], defaultExtName); 64 } else { 65 // 生成的代碼功能等價於下面的代碼: 66 // url.getParameter(value[i], getNameCode) 67 // 以 Transporter 接口的 connect 方法爲例,最終生成的代碼以下: 68 // url.getParameter("client", url.getParameter("transporter", "netty")) 69 getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode); 70 } 71 } else { 72 // 生成的代碼功能等價於下面的代碼: 73 // url.getProtocol() == null ? getNameCode : url.getProtocol() 74 // 以 Protocol 接口的 connect 方法爲例,最終生成的代碼以下: 75 // url.getProtocol() == null ? "dubbo" : url.getProtocol() 76 getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", 77 getNameCode); 78 } 79 } 80 } 81 ...暫時省略....
第8行獲取默認擴展名。來自於@SPI註解的value值。
第13行開始循環value,此處的value是@Adaptive註解的內容,前面有過度析,若是@Adaptive沒有設置value,則經過type名稱解析出value。
上面的代碼分支比較多,可是主要是集中在protocol、hasInvocation的判斷上。
首先看hasInvocation,若是hasInvocation不爲空,咱們看到生成的代碼是「url.getMethodParameter(methodName...」,這個methodName也是前面判斷hasInvocation時獲取到的。這等於在從URL中獲取值的時候,加上了methodName。若是hasInvocation爲空,此時的分支生成的代碼,直接是「url.getParameter(.."。
第二個是判斷protocol的分支,因爲URL類中有單獨的protocol變量,因此 若是value值爲protocol,此時從URL中取值,能夠直接調用url.getProtocol(),不須要經過URL的getParameter方法。
上面的代碼是從URL中獲取擴展點key的主要邏輯,分支比較多,可是不少重複的代碼,也進行了比較詳細的註釋。
咱們接着往下看,從URL中拿到擴展點的key後的代碼
1 code.append("\nString extName = ").append(getNameCode).append(";"); 2 // check extName == null? 3 String s = String.format("\nif(extName == null) " 4 + "throw new IllegalStateException(\"Fail to get extension(%s) name from url(\" + url.toString() + \") use keys(%s)\");", 5 type.getName(), Arrays.toString(value)); 6 code.append(s); 7 // 生成擴展獲取代碼,格式以下: 8 // type 全限定名 extension = (type全限定名)ExtensionLoader全限定名 9 // .getExtensionLoader(type全限定名.class).getExtension(extName); 10 // Tips: 格式化字符串中的 %<s 表示使用前一個轉換符所描述的參數,即 type 全限定名 11 s = String.format("\n%s extension = (%<s)%s.getExtensionLoader(%s.class).getExtension(extName);", 12 type.getName(), ExtensionLoader.class.getSimpleName(), type.getName()); 13 code.append(s); 14 15 // return statement 16 // 若是方法返回值類型非 void,則生成 return 語句。 17 if (!rt.equals(void.class)) { 18 code.append("\nreturn "); 19 } 20 21 s = String.format("extension.%s(", method.getName()); 22 code.append(s); 23 for (int i = 0; i < pts.length; i++) { 24 if (i != 0) 25 code.append(", "); 26 code.append("arg").append(i); 27 } 28 code.append(");");
這段代碼就比較簡單明瞭了,核心在第11行,強制轉換爲擴展點type類型,經過ExtensionLoader的getExtensionLoader獲取type接口對應的ExtensionLoader實例。
如今已經拿到的擴展點實現的key,只要調用ExtensionLoader實例的getExtension()方法,便可返回須要調用的擴展點實現。
咱們分析的主線是按擴展點的一個方法進行,每一個被@Adaptive修飾的方法,生成的邏輯都是同樣的,主要的邏輯是:
一、根據@Adaptive註解的value,或是擴展點type的名稱生成從URL獲取擴展點實現類key的關鍵字
二、根據第一步獲取的關鍵字,從URL中獲取要調用的擴展點實現類的key
三、獲取到擴展點實現類對應的key,調用ExtensionLoader實例的getExtension()方法,便可拿到對應的擴展點實現
四、方法的執行是調用擴展點實現類的目標方法。
至此新類的字符串已經生成了,咱們回到createAdaptiveExtensionClass方法
1 private Class<?> createAdaptiveExtensionClass() { 2 // 建立自適應擴展代碼 字符串 3 String code = createAdaptiveExtensionClassCode(); 4 ClassLoader classLoader = findClassLoader(); 5 // 獲取編譯器實現類 6 com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader 7 .getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension(); 8 // 編譯代碼,獲取自適應擴展類的Class 9 return compiler.compile(code, classLoader); 10 }
第3行就是咱們前面分析的獲取新類字符串的方法,拿到code以後,再獲取類加載器,獲取編輯器,執行編譯。返回的就是自適應擴展類的Class對象。
經過此方法,再往上返回就是自適應擴展類的對象,以及緩存對象等邏輯。自適應擴展的獲取基本就結束了。
經過上面的分析,咱們基本瞭解的自適應擴展點的實現邏輯,難點就是@Adaptive註解方法時,生成新類的字符串之處。別的邏輯還算清晰。若是在讀到此處有困惑,請評論留言,我會進行詳細解釋。