Dubbo源碼分析之SPI(三)

1、概述

  本篇介紹自適應擴展,方法getAdaptiveExtension()的實現。ExtensionLoader類自己不少功能也使用到了自適應擴展。包括ExtensionFactory擴展。java

  通俗的講,自適應擴展要實現的邏輯是:調用擴展點的方法時,自動判斷要調用那個擴展點實現類的方法。咱們知道,一個擴展點一般有多個實現類,在配置文本文件中分多行配置,在前面的分析中,咱們知道經過getExtension(String name)方法,返回的是指定key的擴展點,而自適應擴展點方法getAdaptiveExtension()在調用前,不確認返回那個擴展點。而是在方法調用的時候,根據方法入參,進行肯定,具體是調用那個實現類。數組

  自適應擴展基於@Adaptive註解,能夠修飾類,也能夠修飾方法。修飾類的時候,邏輯比較簡單,不會動態生成代碼邏輯,使用的場景也比較少,主要包括AdaptiveCompiler 和 AdaptiveExtensionFactory。修飾方法的時候,會動態生成一個新類,新類包括擴展點的全部方法,調用getAdaptiveExtension()返回的就是新類對象。緩存

2、詳細介紹

  前面咱們說過,@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修飾類仍是修飾方法,自適應擴展點的返回邏輯,這點是要結合代碼進行說明,接下來就開啓咱們的源代碼分析。

3、源代碼分析

  咱們從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對象。

  經過此方法,再往上返回就是自適應擴展類的對象,以及緩存對象等邏輯。自適應擴展的獲取基本就結束了。

4、總結

  經過上面的分析,咱們基本瞭解的自適應擴展點的實現邏輯,難點就是@Adaptive註解方法時,生成新類的字符串之處。別的邏輯還算清晰。若是在讀到此處有困惑,請評論留言,我會進行詳細解釋。

相關文章
相關標籤/搜索