Dubbo源碼(2)-動態編譯技術源碼解析

本文主要參考自Dubbo官方文檔、Dubbo項目源碼以及網絡文章和相關書籍,並附上自身的一些理解,如有遺漏或錯誤,還望海涵並指出。謝謝!

------本文基於Dubbo-2.6.1版本

一.Javassist動態編譯

1.1、Javassist介紹

Javassist是一款Java字節碼生成和轉化框架,它在Dubbo中起到的作用是動態編譯以及動態代理。

爲什麼需要使用Javassist作爲動態編譯和代理框架呢,原因主要Javassist的性能以及效率都高於JDK自身的框架。

譬如,如果需要在運行期獲取一個特定的類可以採用JDK反射來實現,但是使用反射會有一個缺點—效率低,所以在一些追求效率的場合會使用Javassist直接在內存中生成class字節碼對象供程序來使用,這就是Dubbo使用Javassist的原因。

1.2、入門使用

下面編寫一個入門Demo來演示一下如何使用Javassist在內存中生成一個class,並且將其字節碼輸出到文件中保存起來:

  • TestJavassist
@Test
    public void test_Javassist() throws CannotCompileException, NotFoundException, IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        ClassPool pool = ClassPool.getDefault();
        CtClass cls = pool.makeClass("thinking_in_java.javassist.proxyClass");
        // 添加私有成員name及其getter、setter方法
        CtField param = new CtField(pool.get("java.lang.String"), "name", cls);
        param.setModifiers(Modifier.PRIVATE);
        cls.addMethod(CtNewMethod.setter("setName", param));
        cls.addMethod(CtNewMethod.getter("getName", param));
        cls.addField(param, CtField.Initializer.constant(""));

        // 添加無參的構造體
        CtConstructor cons = new CtConstructor(new CtClass[] {}, cls);
        cons.setBody("{name = \"ARong\";}");
        cls.addConstructor(cons);

        // 添加有參的構造體
        cons = new CtConstructor(new CtClass[] {pool.get("java.lang.String")}, cls);
        cons.setBody("{$0.name = $1;}");
        cls.addConstructor(cons);

        // 打印創建類的類名
        System.out.println(cls.toClass());
        // 輸出字節碼到class中
        byte[] bytes = cls.toBytecode();
        FileOutputStream fos = new FileOutputStream(new File("/Users/arong/MyFile/Project/Algorithm/src/main/java/thinking_in_java/javassist/proxyClass.class"));
        fos.write(bytes);
        fos.flush();
        Object o = Class.forName("thinking_in_java.javassist.proxyClass").newInstance();
        Method getName = o.getClass().getMethod("getName");
        Object res = getName.invoke(o);
        System.out.println(o);
        System.out.println(res);
    }

輸出:

class thinking_in_java.javassist.proxyClass
thinking_in_java.javassist.proxyClass@dfd3711
ARong

在文件夾中查看生成的class字節碼文件(idea自動反編譯成了Java代碼):

  • proxyClass.class
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package thinking_in_java.javassist;

public class proxyClass {
    private String name = "";

    public void setName(String var1) {
        this.name = var1;
    }

    public String getName() {
        return this.name;
    }

    public proxyClass() {
        this.name = "ARong";
    }

    public proxyClass(String var1) {
        this.name = var1;
    }
}

二.Dubbo動態編譯源碼解析

看完了Javassist,我們大概知道其實這個框架的作用就是爲了代替反射,用更高效的方式直接在內存中生成所需的class。

在Dubbo中也大量地使用了Javassist來做動態編譯,譬如在SPI機制中的核心類ExtensionLoader中的生成自適應拓展實現類的createAdaptiveExtensionClass方法就使用了動態編譯來生成適應類,不過Dubbo基於Javassist做了一層封裝:

  • ExtensionLoader
/*
    *   創建自適應拓展類的代碼並進行編譯返回,生成Xxx$Adaptive自適應類(動態編譯)
     */
    private Class<?> createAdaptiveExtensionClass() {
        // 生成自適應類的代碼字符串
        String code = createAdaptiveExtensionClassCode();
        // 獲取ClassLoader
        ClassLoader classLoader = findClassLoader();
        // 對代碼進行編譯,生成相應的Class返回
        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }

3.1、createAdaptiveExtensionClassCode方法

在這個方法中生成了Xxx&Adaptive自適應實現類的代碼字符串,可供後續被Dubbo編譯成真正的Class類:

  • createAdaptiveExtensionClassCode
/*
    *   生成自適應拓展類的代碼
     */
    private String createAdaptiveExtensionClassCode() {
        // 使用StringBuilder來構建代碼字符串
        StringBuilder codeBuidler = new StringBuilder();
        // 檢測方法上是否含有@Adaptive註解,不含有則拋出異常
        Method[] methods = type.getMethods();
        boolean hasAdaptiveAnnotation = false;
        for (Method m : methods) {
            if (m.isAnnotationPresent(Adaptive.class)) {
                hasAdaptiveAnnotation = true;
                break;
            }
        }
        // no need to generate adaptive class since there's no adaptive method found.
        if (!hasAdaptiveAnnotation)
            throw new IllegalStateException("No adaptive method on extension " + type.getName() + ", refuse to create the adaptive class!");
        // 開始構建package、import內容以及類名(Xxx$Adaptive)
        codeBuidler.append("package " + type.getPackage().getName() + ";");
        codeBuidler.append("\nimport " + ExtensionLoader.class.getName() + ";");
        codeBuidler.append("\npublic class " + type.getSimpleName() + "$Adaptive" + " implements " + type.getCanonicalName() + " {");
        //  生成代理所含有的方法
        // 省略...
        return codeBuidler.toString();
    }

想更深入地瞭解具體的代碼生成邏輯可以看官方文檔關於這個方法的解析:
https://dubbo.apache.org/zh-cn/docs/source_code_guide/adaptive-extension.html

在執行完這個方法之後,就會生成一個自適應拓展類的Java代碼字符串了,類似於下面這個形式:

  • Ext2$Adaptive
package com.alibaba.dubbo.common.extensionloader.ext2;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
public class Ext2$Adaptive implements com.alibaba.dubbo.common.extensionloader.ext2.Ext2 {
public java.lang.String bang(com.alibaba.dubbo.common.URL arg0, int arg1) {throw new UnsupportedOperationException("method public abstract java.lang.String com.alibaba.dubbo.common.extensionloader.ext2.Ext2.bang(com.alibaba.dubbo.common.URL,int) of interface com.alibaba.dubbo.common.extensionloader.ext2.Ext2 is not adaptive method!");
}
public java.lang.String echo(com.alibaba.dubbo.common.extensionloader.ext2.UrlHolder arg0, java.lang.String arg1) {
if (arg0 == null) throw new IllegalArgumentException("com.alibaba.dubbo.common.extensionloader.ext2.UrlHolder argument == null");
if (arg0.getUrl() == null) throw new IllegalArgumentException("com.alibaba.dubbo.common.extensionloader.ext2.UrlHolder argument getUrl() == null");com.alibaba.dubbo.common.URL url = arg0.getUrl();
String extName = url.getParameter("ext2");
if(extName == null) throw new IllegalStateException("Fail to get extension(com.alibaba.dubbo.common.extensionloader.ext2.Ext2) name from url(" + url.toString() + ") use keys([ext2])");
com.alibaba.dubbo.common.extensionloader.ext2.Ext2 extension = (com.alibaba.dubbo.common.extensionloader.ext2.Ext2)ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.extensionloader.ext2.Ext2.class).getExtension(extName);
return extension.echo(arg0, arg1);
}
}

3.2、Compiler接口

接下來就是重點了:如何將Java代碼字符串編譯成可供使用的Class?如下面的代碼:

  • ExtensionLoader
/*
    *   創建自適應拓展類的代碼並進行編譯返回,生成Xxx$Adaptive自適應類(動態編譯)
     */
    private Class<?> createAdaptiveExtensionClass() {
        // 生成自適應類的代碼字符串
        String code = createAdaptiveExtensionClassCode();
        // 獲取ClassLoader
        ClassLoader classLoader = findClassLoader();
        // 對代碼進行編譯,生成相應的Class返回
        com.alibaba.dubbo.common.compiler.Compiler compiler = ExtensionLoader.getExtensionLoader(com.alibaba.dubbo.common.compiler.Compiler.class).getAdaptiveExtension();
        return compiler.compile(code, classLoader);
    }

這就是Complier接口做的事情,Complier接口的全限定名:

com.alibaba.dubbo.common.compiler.Compiler
  • Compiler
@SPI("javassist")
public interface Compiler {

    /**
     * Compile java source code.
     *
     * 編譯 Java 代碼字符串
     *
     * @param code        Java source code
     *                    Java 代碼字符串
     * @param classLoader classloader
     *                    類加載器
     * @return Compiled class
     *                    編譯後的類
     */
    Class<?> compile(String code, ClassLoader classLoader);
}

可以看出Compiler接口也是使用了SPI機制的,其默認拓展實現類爲javassist這個拓展名映射的實現類,
查閱resource/META-INF/dubbo查看Compiler接口所有拓展實現類:

  • META-INF/dubbo/internal/com.alibaba.dubbo.common.compiler.Compiler
adaptive=com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler
jdk=com.alibaba.dubbo.common.compiler.support.JdkCompiler
javassist=com.alibaba.dubbo.common.compiler.support.JavassistCompiler

在這裏插入圖片描述

有效代碼量750行左右:

在這裏插入圖片描述

3.2.1、AdaptiveCompiler

com.alibaba.dubbo.common.compiler.support.AdaptiveCompiler ,實現 Compiler 接口,自適應 Compiler 實現類。

  • AdaptiveComiler
@Adaptive
public class AdaptiveCompiler implements Compiler {
    // 默認的編譯器名稱
    private static volatile String DEFAULT_COMPILER;
    // 可指定使用編譯器
    public static void setDefaultCompiler(String compiler) {
        DEFAULT_COMPILER = compiler;
    }

    
    public Class<?> compile(String code, ClassLoader classLoader) {
        Compiler compiler;
        // 獲取ExtensionLoader
        ExtensionLoader<Compiler> loader = ExtensionLoader.getExtensionLoader(Compiler.class);
        // 編譯器名稱設置爲默認
        String name = DEFAULT_COMPILER; // copy reference
        if (name != null && name.length() > 0) {
            // 使用指定的編譯器
            compiler = loader.getExtension(name);
        } else {
            // 使用默認編譯器(Javassist)
            compiler = loader.getDefaultExtension();
        }
        // 編譯生成Class
        return compiler.compile(code, classLoader);
    }
}

從AdaptiveCompiler的代碼中可以看出,獲取到自適應的Compiler,默認情況下也是獲取到Javassist作爲拓展實現的,當然也可以指定其他實現,譬如JDK。

如果需要指定JDK Compiler,可通過下述配置:

<dubbo:application compiler="jdk" />

這個配置會觸發ApplicationConfig的setCompiler方法,該方法會調用setDefaultCompiler設置編譯器:

  • ApplicationConfig
public void setCompiler(String compiler) {
    this.compiler = compiler;
    AdaptiveCompiler.setDefaultCompiler(compiler);
}
3.2.2、AbstractCompiler

com.alibaba.dubbo.common.compiler.support.AbstractCompiler是Compiler接口的實現類,使用模板方法模式,定義doCompile方法,實質上依賴於JavassistCompilerJdkCompiler的實現:

  • AbstractCompiler
public abstract class AbstractCompiler implements Compiler {

    // 定義package正則匹配
    private static final Pattern PACKAGE_PATTERN = Pattern.compile("package\\s+([$_a-zA-Z][$_a-zA-Z0-9\\.]*);");
    // 定義class正則匹配
    private static final Pattern CLASS_PATTERN = Pattern.compile("class\\s+([$_a-zA-Z][$_a-zA-Z0-9]*)\\s+");
    /*
    *   編譯code生成Class
     */
    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 {
            // 嘗試使用反射直接加載該Class,如果不存在則拋出異常,進入catch塊中
            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 {
                // 模板方法模式
                // 使用子類實現的doCompile方法編譯生成Class
                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));
            }
        }
    }

    /*
    *   由子類提供具體實現
     */
    protected abstract Class<?> doCompile(String name, String source) throws Throwable;

}
3.2.3、JavassistCompiler
public class JavassistCompiler extends AbstractCompiler {

    // import匹配
    private static final Pattern IMPORT_PATTERN = Pattern.compile("import\\s+([\\w\\.\\*]+);\n");
    // extends匹配
    private static final Pattern EXTENDS_PATTERN = Pattern.compile("\\s+extends\\s+([\\w\\.]+)[^\\{]*\\{\n");
    // implements匹配
    private static final Pattern IMPLEMENTS_PATTERN = Pattern.compile("\\s+implements\\s+([\\w\\.]+)\\s*\\{\n");
    // 方法匹配
    private static final Pattern METHODS_PATTERN = Pattern.compile("\n(private|public|protected)\\s+");
    // 變量匹配
    private static final Pattern FIELD_PATTERN = Pattern.compile("[^\n]+=[^\n]+;");

    /*
    *   name:類全限定名
    *   source:Java代碼
     */
    @Override
    public Class<?> doCompile(String name, String source) throws Throwable {
        int i = name.lastIndexOf('.');
        // 獲取到類名稱
        String className = i < 0 ? name : name.substring(i + 1);
        // 創建Javassist ClassPool,開始構建Class
        ClassPool pool = new ClassPool(true);
        // 設置類搜索路徑
        pool.appendClassPath(new LoaderClassPath(ClassHelper.getCallerClassLoader(getClass())));
        // 匹配 import
        Matcher matcher = IMPORT_PATTERN.matcher(source);
        // 引用的包名
        List<String> importPackages = new ArrayList<String>();
        // 引用的類名
        Map<String, String> fullNames = new HashMap<String, String>();
        while (matcher.find()) {
            String pkg = matcher.group(1);
            // 引用整個包下的類和接口
            if (pkg.endsWith(".*")) {
                String pkgName = pkg.substring(0, pkg.length() - 2);
                pool.importPackage(pkgName);
                importPackages.add(pkgName);
            } else {
                // 引用指定類和接口
                int pi = pkg.lastIndexOf('.');
                if (pi > 0) {
                    String pkgName = pkg.substring(0, pi);
                    pool.importPackage(pkgName);
                    importPackages.add(pkgName);
                    fullNames.put(pkg.substring(pi + 1), pkg);
                }
            }
        }
        // 獲取到所有需要import的類
        String[] packages = importPackages.toArray(new String[0]);
        matcher = EXTENDS_PATTERN.matcher(source);
        CtClass cls;
        if (matcher.find()) {
            String extend = matcher.group(1).trim();
            String extendClass;
            if (extend.contains(".")) {
                extendClass = extend;
            } else if (fullNames.containsKey(extend)) {
                extendClass = fullNames.get(extend);
            } else {
                extendClass = ClassUtils.forName(packages, extend).getName();
            }
            // 爲cls添加extends的類
            cls = pool.makeClass(name, pool.get(extendClass));
        } else {
            cls = pool.makeClass(name);
        }
        matcher = IMPLEMENTS_PATTERN.matcher(source);
        if (matcher.find()) {
            String[] ifaces = matcher.group(1).trim().split("\\,");
            for (String iface : ifaces) {
                iface = iface.trim();
                String ifaceClass;
                if (iface.contains(".")) {
                    ifaceClass = iface;
                } else if (fullNames.containsKey(iface)) {
                    ifaceClass = fullNames.get(iface);
                } else {
                    ifaceClass = ClassUtils.forName(packages, iface).getName();
                }
                // 爲cls添加implement的接口
                cls.addInterface(pool.get(ifaceClass));
            }
        }
        // 開始匹配類的主體構造方法、public方法和private方法
        String body = source.substring(source.indexOf("{") + 1, source.length() - 1);
        String[] methods = METHODS_PATTERN.split(body);
        // 匹配完方法,併爲cls添加上去
        for (String method : methods) {
            method = method.trim();
            if (method.length() > 0) {
                // 構造方法
                if (method.startsWith(className)) {
                    cls.addConstructor(CtNewConstructor.make("public " + method, cls));
                } else if (FIELD_PATTERN.matcher(method).matches()) {
                    // 私有方法
                    cls.addField(CtField.make("private " + method, cls));
                } else {
                    // 公有方法
                    cls.addMethod(CtNewMethod.make("public " + method, cls));
                }
            }
        }
        // 通過cls創建Class
        return cls.toClass(ClassHelper.getCallerClassLoader(getClass()), JavassistCompiler.class.getProtectionDomain());
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章