1. 程式人生 > >dubbo源碼閱讀之自適應擴展

dubbo源碼閱讀之自適應擴展

oid stc 接口 rap test 情況下 獲取url參數 find 接口類

自適應擴展機制

剛開始看代碼,其實並不能很好地理解dubbo的自適應擴展機制的作用,我們不妨先把代碼的主要邏輯過一遍,梳理一下,在了解了代碼細節之後,回過頭再來思考自適應擴展的作用,dubbo為什麽要設計這樣一種擴展機制??它的優點在什麽地方??

Adaptive註解

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Adaptive {
/**
 * Decide which target extension to be injected. The name of the target extension is decided by the parameter passed
 * in the URL, and the parameter names are given by this method.
 * 該值決定了哪個擴展類會被自動註入。目標擴展名由傳入的URL中的參數和Adaptive註解中的value值共同決定。
 * value相當於一些key值,用這些key值從URL中獲取相應的value值,最終決定擴展名。
 * <p>
 * If the specified parameters are not found from {@link URL}, then the default extension will be used for
 * dependency injection (specified in its interface's {@link SPI}).
 * 如果傳入的URL中沒有符合條件的擴展名,那麽使用默認擴展名(默認擴展名由SPI註解的value決定)
 * <p>
 * For examples, given <code>String[] {"key1", "key2"}</code>:
 * 例如,value的值是:String[] {"key1", "key2"}
 * <ol>
 * <li>find parameter 'key1' in URL, use its value as the extension's name</li>
 * 首先在URL中查找key1,如果存在使用key1作為擴展名
 * <li>try 'key2' for extension's name if 'key1' is not found (or its value is empty) in URL</li>
 * 如果URL中找不到key1,查找key2,如果能找到使用key2作為擴展名
 * <li>use default extension if 'key2' doesn't appear either</li>
 * 如果key2也找不到,那麽使用默認擴展名
 * <li>otherwise, throw {@link IllegalStateException}</li>
 * 如果默認擴展名為空,則拋異常
 * </ol>
 * If default extension's name is not give on interface's {@link SPI}, then a name is generated from interface's
 * class name with the rule: divide classname from capital char into several parts, and separate the parts with
 * dot '.', for example: for {@code org.apache.dubbo.xxx.YyyInvokerWrapper}, its default name is
 * <code>String[] {"yyy.invoker.wrapper"}</code>. This name will be used to search for parameter from URL.
 * 如果SPI註解中沒有給出默認擴展名,就會通過如下規則生成默認擴展名:將駝峰式風格的接口名轉換成.號分隔的名稱作為擴展名,例如
 * 接口名org.apache.dubbo.xxx.YyyInvokerWrapper會被轉換成yyy.invoker.wrapper作為擴展名。用這個名稱在URL參數中查找。
 *
 *
 * @return parameter key names in URL
 */
String[] value() default {};

}

getAdaptiveExtension

我們先從獲取自適應擴展類的入口方法看起,

public T getAdaptiveExtension() {
    //檢查緩存
    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;
}

主要邏輯就是檢查緩存,如果緩存不錯存在則加載自適應擴展類,使用雙檢查鎖機制保證在多線程的情況下不會重復加載。都是慣用法,沒什麽可說的。

createAdaptiveExtension

private T createAdaptiveExtension() {
    try {
        //1. 獲取自適應擴展類並實例化
        //2. 自動註入屬性值
        return injectExtension((T) getAdaptiveExtensionClass().newInstance());
    } catch (Exception e) {
        throw new IllegalStateException("Can't create adaptive extension " + type + ", cause: " + e.getMessage(), e);
    }
}

這裏使用了dubbo實現的IOC功能,ExtensionLoader的IOC特性我們再上一節已經說過,通過ExtensionFactory接口的擴展類實現對屬性的自動註入,不再贅述。
我們的重點還是放在如何生成自適應擴展類上,即getAdaptiveExtensionClass的邏輯。

getAdaptiveExtensionClass

private Class<?> getAdaptiveExtensionClass() {
    //首先查找所有 資源文件,加載全部擴展類,但並未實例化
    // 在加載擴展類的過程中,如果遇到帶Adaptive註解的類,會把該類設到緩存cachedAdaptiveClass
    //這種情況,由用戶來實現自適應擴展的邏輯,比較簡單
    getExtensionClasses();
    if (cachedAdaptiveClass != null) {
        return cachedAdaptiveClass;
    }
    //大部分情況下,自適應擴展的邏輯不是由用戶實現,而是由框架自動生成的,生成的邏輯就在下面的方法中
    return cachedAdaptiveClass = createAdaptiveExtensionClass();
}

沒啥好說的,繼續看下一個方法

createAdaptiveExtensionClass

//重點來了,這個方法定義了生成自適應擴展類代碼的總體方案,
//這個方法引出了自適應擴展機制的核心類AdaptiveClassCodeGenerator
private Class<?> createAdaptiveExtensionClass() {
    //AdaptiveClassCodeGenerator類是關鍵代碼
    String code = new AdaptiveClassCodeGenerator(type, cachedDefaultName).generate();
    ClassLoader classLoader = findClassLoader();
    //Compiler接口也是通過dubbo自己的spi機制加載的,
    // Compiler接口的自適應擴展類是預先寫好的代碼,即AdaptiveCompiler
    // 這裏有一個有意思的問題,如果compiler的自適應擴展類也由框架自動生成,那麽這裏就會出現循環調用
    org.apache.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
    //返回編譯後的Class對象
    return compiler.compile(code, classLoader);
}

AdaptiveClassCodeGenerator.generate

生成自適應擴展類代碼的核心邏輯被封裝在AdaptiveClassCodeGenerator類中,AdaptiveClassCodeGenerator的構造方法僅僅設置了要擴展的接口類型以及默認擴展類名(可能為空),我們直接從generate方法看起,

/**
 * generate and return class code
 * 生成自適應擴展類代碼
 */
public String generate() {
    // no need to generate adaptive class since there's no adaptive method found.
    // 如果接口中沒有帶Adaptive註解的方法,說名該類型不需要自適應擴展,直接拋異常
    if (!hasAdaptiveMethod()) {
        throw new IllegalStateException("No adaptive method exist on extension " + type.getName() + ", refuse to create the adaptive class!");
    }

    StringBuilder code = new StringBuilder();
    // 生成package信息,與接口的包名相同
    code.append(generatePackageInfo());
    //生成import信息, 生成了如下的import語句
    //import org.apache.dubbo.common.extension.ExtensionLoader
    code.append(generateImports());
    //生成類定義信息
    // public class <接口名>$Adaptive implements <接口全限定名> {
    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();
}

總體來看,邏輯還是比較簡單的,就是我們平時寫代碼的步驟,依次生成包信息,import內容,類的具體代碼包括每個方法的實現代碼。
其中比較重要的是生成方法的實現代碼,我們重點看一下這段代碼。

generateMethod

private String generateMethod(Method method) {
    // 方法的返回類型
    String methodReturnType = method.getReturnType().getCanonicalName();
    // 方法名稱
    String methodName = method.getName();
    // 方法體,即方法的實現代碼,這一步最關鍵
    String methodContent = generateMethodContent(method);
    // 方法參數,
    // (ParamType0 arg0, ParamType1 arg1, ParamType2 arg2)
    String methodArgs = generateMethodArguments(method);
    // 異常信息,throws內容
    String methodThrows = generateMethodThrows(method);
    //最後按照java代碼的規範拼接成一個完整的方法代碼
    return String.format(CODE_METHOD_DECLARATION, methodReturnType, methodName, methodArgs, methodThrows, methodContent);
}

重點是生成方法具體實現代碼的邏輯,generateMethodContent方法。

generateMethodContent

生成方法體的邏輯。這個方法比較長,我們分開來看。

/**
 * generate method content
 * 生成方法體
 */
private String generateMethodContent(Method method) {
    Adaptive adaptiveAnnotation = method.getAnnotation(Adaptive.class);
    // 這一句應該放到else分支內部
    StringBuilder code = new StringBuilder(512);
    // 如果方法不帶Adaptive註解,就直接生成拋Unsupported異常的實現,
    // 也就是說,自適應擴展機制必須要在需要自適應的方法上加Adaptive註解
    if (adaptiveAnnotation == null) {
        return generateUnsupported(method);
    } else {
        // 找到URL類型的參數的下標
        int urlTypeIndex = getUrlTypeIndex(method);

        // found parameter in URL type
        if (urlTypeIndex != -1) {
            // Null Point check
            // 如果方法參數列表中有URL類型的參數,那麽添加對參數做非空檢查的代碼
            code.append(generateUrlNullCheck(urlTypeIndex));
        } else {
            // did not find parameter in URL type
            // 如果方法參數中沒有URL類型,那麽查找能夠簡介獲取到URL的參數,這個參數類型應該含有返回類型的是URL的方法
            code.append(generateUrlAssignmentIndirectly(method));
        }

        // 獲取Adaptive註解的value
        // 如果Adaptive註解的value為空,獲取會麻煩一些,將接口的駝峰式名稱轉換成.分隔的名稱
        // 如LoadBalance轉換成load.balance
        String[] value = getMethodAdaptiveValue(adaptiveAnnotation);

        // 檢查Invocation類型的參數
        boolean hasInvocation = hasInvocationArgument(method);

        // 生成檢查Invocation類型參數非空的代碼段,
        // 只檢查第一個參數??Invocation類型的參數只會有一個??
        code.append(generateInvocationArgumentNullCheck(method));

        // 生成獲取擴展名的代碼段,
        // 擴展名就是資源文件中以k-v存儲的key值
        code.append(generateExtNameAssignment(value, hasInvocation));
        // check extName == null?
        // 生成擴展名非空檢查語句
        code.append(generateExtNameNullCheck(value));

        // 獲取到擴展名後,就可以生成擴展類的獲取語句了
        code.append(generateExtensionAssignment());

        // return statement
        // 最後加上函數調用和返回語句
        code.append(generateReturnAndInovation(method));
    }

    return code.toString();
}

生成的方法體大致分為三塊

  • 獲取URL的值。如果參數中有URL類型直接賦值;如果沒有就間接獲取,查找參數中包含返回URL類型的方法,調用這個方法。此外加上一些非空判斷語句。
  • 獲取擴展名。這一步比較復雜,要考慮的情況比較多。如果Adaptive註解中有protocol就要調用URL的getProtocol方法;用以Adaptive的value數組中的key值依次去URL中獲取參數,直到獲取到不為空的參數。加上擴展名的非空檢查語句。
  • 通過ExtensionLoader獲取擴展名對應的擴展類,賦值給變量extension
  • 最後調用擴展類的相應方法,如果返回值不是void就加上return。

其中獲取擴展名代碼的生成邏輯較為復雜,我們看一下這段代碼。

generateExtNameAssignment

private String generateExtNameAssignment(String[] value, boolean hasInvocation) {
    // TODO: refactor it
    String getNameCode = null;
    // 從後向前遍歷可選的擴展名
    for (int i = value.length - 1; i >= 0; --i) {
        // 對於最後一個value值做如下處理
        if (i == value.length - 1) {
            if (null != defaultExtName) {
                // 如果不是protocol
                if (!"protocol".equals(value[i])) {
                    if (hasInvocation) {
                        // 如果存在Invocation參數,生成獲取方法參數的代碼段
                        getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                    } else {
                        getNameCode = String.format("url.getParameter(\"%s\", \"%s\")", value[i], defaultExtName);
                    }
                } else {
                    // 如果是protocol,生成擴區protocol的代碼段
                    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 {
                        getNameCode = String.format("url.getParameter(\"%s\")", value[i]);
                    }
                } else {
                    getNameCode = "url.getProtocol()";
                }
            }
            //對於不是最後一個的value值做如下處理
        } else {
            if (!"protocol".equals(value[i])) {
                if (hasInvocation) {
                    getNameCode = String.format("url.getMethodParameter(methodName, \"%s\", \"%s\")", value[i], defaultExtName);
                } else {
                    getNameCode = String.format("url.getParameter(\"%s\", %s)", value[i], getNameCode);
                }
            } else {
                getNameCode = String.format("url.getProtocol() == null ? (%s) : url.getProtocol()", getNameCode);
            }
        }
    }

    // 拼接成賦值語句
    // String extName = %s;
    return String.format(CODE_EXT_NAME_ASSIGNMENT, getNameCode);
}

代碼生成示例

這樣光看代碼很難對這塊代碼有直觀的認識,理解也不夠深刻,既然這段代碼的作用主要是生成代碼,那麽我們就定義一個自己的接口,並實現一個擴展類,用AdaptiveClassCodeGenerator來生成一下,看生成的代碼到底是什麽樣子。

  • 首先是接口

      package org.apache.dubbo.common.extension.adaptive.codeGen;
    
      import org.apache.dubbo.common.URL;
      import org.apache.dubbo.common.extension.Adaptive;
      import org.apache.dubbo.common.extension.SPI;
    
      @SPI("default")
      public interface MyExtension {
    
      @Adaptive
      String adaptiveMethod(String param1, int param2, URL url) throws Exception;
    
      @Adaptive("protocol")
      String adaptiveMethod2(String param1, int param2, URLGetter urlGetter) throws Exception;
    
      void noAdaptiveMethod(String param1, URLGetter urlGetter) throws Exception;
      }
  • 實現類

      package org.apache.dubbo.common.extension.adaptive.codeGen.impl;
    
      import org.apache.dubbo.common.URL;
      import org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension;
      import org.apache.dubbo.common.extension.adaptive.codeGen.URLGetter;
    
      public class DefaultMyExtension implements MyExtension {
          @Override
          public String adaptiveMethod(String param1, int param2, URL url) {
              return String.format("Params passed in are: param1:%s, param2:%d, url:%s");
          }
    
          @Override
          public String adaptiveMethod2(String param1, int param2, URLGetter urlGetter) throws Exception {
              return String.format("Params passed in are: param1:%s, param2:%d, urlGetter:%s");
          }
    
          @Override
          public void noAdaptiveMethod(String param1, URLGetter urlGetter) throws Exception {
    
          }
      }
  • URL獲取類。為了測試簡介獲取URL

      package org.apache.dubbo.common.extension.adaptive.codeGen;
    
      import org.apache.dubbo.common.URL;
    
      public class URLGetter {
          private URL url;
    
          public URLGetter(URL url){
              this.url=url;
          }
    
          public URL getUrl() {
              return url;
          }
    
          public void setUrl(URL url) {
              this.url = url;
          }
    
          @Override
          public String   toString() {
              return "URLGetter{" +
                      "url=" + url +
                      '}';
          }
      }
  • 測試類

      package org.apache.dubbo.common.extension.adaptive.codeGen;
    
      import org.apache.dubbo.common.extension.AdaptiveClassCodeGenerator;
      import org.junit.jupiter.api.Test;
    
      public class AdaptiveClassCodeGeneratorTest {
    
          @Test
          public void testProtocolCodeGen() {
              AdaptiveClassCodeGenerator generator=
                      new AdaptiveClassCodeGenerator(MyExtension.class,"default");
              String code=generator.generate();
              System.out.println(code);
          }
      }
  • 生成的代碼。也就是自適應擴展類

      package org.apache.dubbo.common.extension.adaptive.codeGen;
    
      import org.apache.dubbo.common.extension.ExtensionLoader;
    
      public class MyExtension$Adaptive implements org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension {
          public java.lang.String adaptiveMethod(java.lang.String arg0, int arg1, org.apache.dubbo.common.URL arg2) throws java.lang.Exception {
              if (arg2 == null) throw new IllegalArgumentException("url == null");
              org.apache.dubbo.common.URL url = arg2;
              // Adaptive註解中沒有value,用接口名稱自動生成的key值
              String extName = url.getParameter("my.extension", "default");
              if (extName == null)
                  throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension) name from url (" + url.toString() + ") use keys([my.extension])");
              org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension extension = (org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension) ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension.class).getExtension(extName);
              return extension.adaptiveMethod(arg0, arg1, arg2);
          }
    
          public java.lang.String adaptiveMethod2(java.lang.String arg0, int arg1, org.apache.dubbo.common.extension.adaptive.codeGen.URLGetter arg2) throws java.lang.Exception {
              if (arg2 == null)
                  throw new IllegalArgumentException("org.apache.dubbo.common.extension.adaptive.codeGen.URLGetter argument == null");
              if (arg2.getUrl() == null)
                  throw new IllegalArgumentException("org.apache.dubbo.common.extension.adaptive.codeGen.URLGetter argument getUrl() == null");
              // 間接獲取URL
              org.apache.dubbo.common.URL url = arg2.getUrl();
              // 如果有protocol的key,那麽調用URL.getProtocol方法獲取協議名稱
              String extName = (url.getProtocol() == null ? "default" : url.getProtocol());
              if (extName == null)
                  throw new IllegalStateException("Failed to get extension (org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension) name from url (" + url.toString() + ") use keys([protocol])");
              org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension extension = (org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension) ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension.class).getExtension(extName);
              return extension.adaptiveMethod2(arg0, arg1, arg2);
          }
    
          // 為標Adaptive註解的方法直接拋異常
          public void noAdaptiveMethod(java.lang.String arg0, org.apache.dubbo.common.extension.adaptive.codeGen.URLGetter arg1) throws java.lang.Exception {
              throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension.noAdaptiveMethod(java.lang.String,org.apache.dubbo.common.extension.adaptive.codeGen.URLGetter) throws java.lang.Exception of interface org.apache.dubbo.common.extension.adaptive.codeGen.MyExtension is not adaptive method!");
          }
      }

這樣看是不是直觀多了。回過頭再去看一遍代碼生成邏輯,就好懂多了。

編譯加載

代碼生成之後,還需要將代碼編譯為字節碼並通過類加載器加載到虛擬機中,獲取Class對象,然後才能使用。
負責編譯的是org.apache.dubbo.common.compiler.Compiler接口,這個接口的實現類也是通過ExtensionLoader進行加載的,見ExtensionLoader.createAdaptiveExtensionClass方法。
我們看一下Compiler接口

Compiler

// 默認擴展名是javassist
@SPI("javassist")
public interface Compiler {

    /**
     * Compile java source code.
     *
     * @param code        Java source code
     * @param classLoader classloader
     * @return Compiled class
     */
    Class<?> compile(String code, ClassLoader classLoader);

}

另外,我們再看一下dubbo-common模塊中META-INF/dubbo/inetrnal/org.apache.dubbo.common.compiler.Compiler文件中的內容:

adaptive=org.apache.dubbo.common.compiler.support.AdaptiveCompiler
jdk=org.apache.dubbo.common.compiler.support.JdkCompiler
javassist=org.apache.dubbo.common.compiler.support.JavassistCompiler

AdaptiveCompiler類上帶有Adaptive註解,所以ExtensionLoader在加載時AdaptiveCompiler類會被設置為自適應擴展類。而在createAdaptiveExtensionClass方法中真是通過Compiler接口的自適應擴展類來進行編譯。那麽我們看一下AdaptiveCompiler中的邏輯。

AdaptiveCompiler

public Class<?> compile(String code, ClassLoader classLoader) {
    Compiler compiler;
    ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
    // 可以通過設置靜態變量DEFAULT_COMPILER指定默認擴展類
    String name = DEFAULT_COMPILER; // copy reference
    if (name != null && name.length() > 0) {
        compiler = loader.getExtension(name);
    } else {
        // 如果未指定,則獲取SPI註解指定的默認擴展類
        compiler = loader.getDefaultExtension();
    }
    return compiler.compile(code, classLoader);
}

由Compiler接口上的SPI註解,我們知道默認的實現類是JavassistCompiler,所以我們看一下這個類是怎麽進行編譯的。

JavassistCompiler

JavassistCompiler繼承自AbstractCompiler,有一部分邏輯在AbstractCompiler類中,所以我們先看一下

AbstractCompiler.compile

private static final Pattern PACKAGE_PATTERN = Pattern.compile("package\\s+([$_a-zA-Z][$_a-zA-Z0-9\\.]*);");

private static final Pattern CLASS_PATTERN = Pattern.compile("class\\s+([$_a-zA-Z][$_a-zA-Z0-9]*)\\s+");    

public Class<?> compile(String code, ClassLoader classLoader) {
    //去除兩端空格
    code = code.trim();
    // 匹配包名
    Matcher matcher = PACKAGE_PATTERN.matcher(code);
    String pkg;
    if (matcher.find()) {
        pkg = matcher.group(1);
    } else {
        pkg = "";
    }
    // 匹配類名
    matcher = CLASS_PATTERN.matcher(code);
    String cls;
    if (matcher.find()) {
        cls = matcher.group(1);
    } else {
        throw new IllegalArgumentException("No such class name in " + code);
    }
    // 根據包名和類名拼接類的全限定名
    String className = pkg != null && pkg.length() > 0 ? pkg + "." + cls : cls;
    try {
        // 嘗試直接加載???類的字節碼已經存在??或者類已經被加載過了??
        // 什麽情況下會出現這種情況??
        return Class.forName(className, true, ClassHelper.getCallerClassLoader(getClass()));
    } catch (ClassNotFoundException e) {
        // 感覺沒必要多這一句吧??
        // 代碼合法性檢查應該在生成代碼的方法中做,這地方單獨做一個花括號檢查沒什麽意義吧?
        if (!code.endsWith("}")) {
            throw new IllegalStateException("The java code not endsWith \"}\", code: \n" + code + "\n");
        }
        try {
            // 由子類實現
            return doCompile(className, code);
        } catch (RuntimeException t) {
            throw t;
        } catch (Throwable t) {
            throw new IllegalStateException("Failed to compile class, cause: " + t.getMessage() + ", class: " + className + ", code: \n" + code + "\n, stack: " + ClassUtils.toString(t));
        }
    }
}

主要就是拼接處類的全限定名,然後做一些檢查,嘗試直接加載類。真正的編譯過程交由子類實現。

JavassistCompiler.doCompile

@Override
public Class<?> doCompile(String name, String source) throws Throwable {
    // 實際執行編譯的類
    CtClassBuilder builder = new CtClassBuilder();
    // 設置類名
    builder.setClassName(name);

    // process imported classes
    // 匹配出所有的import語句,並設置到CtClassBuilder對象中
    Matcher matcher = IMPORT_PATTERN.matcher(source);
    while (matcher.find()) {
        builder.addImports(matcher.group(1).trim());
    }
    
    // process extended super class
    // 匹配出extends的類型,設置到CtClassBuilder對象中
    matcher = EXTENDS_PATTERN.matcher(source);
    if (matcher.find()) {
        builder.setSuperClassName(matcher.group(1).trim());
    }
    
    // process implemented interfaces
    // 匹配出implements的類型,設置到CtClassBuilder對象中
    matcher = IMPLEMENTS_PATTERN.matcher(source);
    if (matcher.find()) {
        String[] ifaces = matcher.group(1).trim().split("\\,");
        Arrays.stream(ifaces).forEach(i -> builder.addInterface(i.trim()));
    }
    
    // process constructors, fields, methods
    // 添加方法和域
    String body = source.substring(source.indexOf('{') + 1, source.length() - 1);
    String[] methods = METHODS_PATTERN.split(body);
    String className = ClassUtils.getSimpleClassName(name);
    Arrays.stream(methods).map(String::trim).filter(m -> !m.isEmpty()).forEach(method-> {
        if (method.startsWith(className)) {
            builder.addConstructor("public " + method);
        } else if (FIELD_PATTERN.matcher(method).matches()) {
            builder.addField("private " + method);
        } else {
            builder.addMethod("public " + method);
        }
    });
    
    // compile
    ClassLoader classLoader = ClassHelper.getCallerClassLoader(getClass());
    // 進行編譯,實際的編譯過程設計到字節碼操作,由javassist庫實現,這裏不再深入下去
    CtClass cls = builder.build(classLoader);
    return cls.toClass(classLoader, JavassistCompiler.class.getProtectionDomain());
}

實際的編譯過程設計到字節碼操作,由javassist庫實現,這裏不再深入下去。

總結

自適應擴展模塊主要分為兩塊,一是生成自適應擴展類的代碼,二是將代碼進行編譯並加載編譯後的字節碼。

  • 其中dubbo實現的主要邏輯就是代碼生成。
    自適應加載擴展類的主要思路就是從方法的參數中獲取URL參數,可以是方法參數本省,也可以從參數的類型的方法中獲取;
    然後從URL參數中查找擴展名(查找的範圍通過Adaptive註解的value值和SPI註解中的默認值組成),找到擴展名後再由ExtensionLoader根據擴展名加載對應的擴展類,
    然後調用對應擴展類的對應方法並返回。

  • 編譯代碼的邏輯主要有javassist庫實現,dubbo在javassist庫基礎上進行了一些簡單封裝,大部分的邏輯都是直接調用javassist庫,這部分不再深入下去,如果對java代碼編譯感興趣可以細看。

dubbo源碼閱讀之自適應擴展