Dubbo原始碼之動態編譯

擊水三千里發表於2019-03-28

Dubbo裡的代理類

JavassistProxyFactory:利用位元組碼技術來建立物件

public <T> T getProxy(Invoker<T> invoker,Class<?>[] interfaces) {

       return (T) Proxy.getProxy(interfaces).newInstance(newInvokerInvocationHndler(invoker));

}

看似跟jdk生成代理一樣, 其實這裡的Proxy類不是jdk中自帶那個生成代理物件的類是com.alibaba.dubbo.common.bytecode.Proxy。

這個dubbo自己寫的Proxy類,利用要代理的介面利用javassist工具生成代理程式碼。
 

什麼是Javassist

package com.soa.other.compiler;

import java.io.File;
import java.io.FileOutputStream;
import java.lang.reflect.Modifier;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewMethod;

/**
 * Javassist是一款位元組碼編輯工具,同時也是一個動態類庫,它可以直接檢查、修改以及建立 Java類。
 * 以下例子就是建立一個動態類
 * 
 */
public class CompilerByJavassist {

	public static void main(String[] args) throws Exception {

		// ClassPool:CtClass物件的容器
		ClassPool pool = ClassPool.getDefault();

		// 通過ClassPool生成一個public新類Emp.java
		CtClass ctClass = pool.makeClass("com.study.javassist.Emp");

		// 新增屬性
		// 首先新增屬性private String ename
		CtField enameField = new CtField(pool.getCtClass("java.lang.String"),
				"ename", ctClass);
		enameField.setModifiers(Modifier.PRIVATE);
		ctClass.addField(enameField);

		// 其次新增熟悉privtae int eno
		CtField enoField = new CtField(pool.getCtClass("int"), "eno", ctClass);
		enoField.setModifiers(Modifier.PRIVATE);
		ctClass.addField(enoField);

		// 為屬性ename和eno新增getXXX和setXXX方法
		ctClass.addMethod(CtNewMethod.getter("getEname", enameField));
		ctClass.addMethod(CtNewMethod.setter("setEname", enameField));
		ctClass.addMethod(CtNewMethod.getter("getEno", enoField));
		ctClass.addMethod(CtNewMethod.setter("setEno", enoField));

		// 新增建構函式
		CtConstructor ctConstructor = new CtConstructor(new CtClass[] {},
				ctClass);
		// 為建構函式設定函式體
		StringBuffer buffer = new StringBuffer();
		buffer.append("{\n").append("ename=\"yy\";\n").append("eno=001;\n}");
		ctConstructor.setBody(buffer.toString());
		// 把建構函式新增到新的類中
		ctClass.addConstructor(ctConstructor);

		// 新增自定義方法
		CtMethod ctMethod = new CtMethod(CtClass.voidType, "printInfo",
				new CtClass[] {}, ctClass);
		// 為自定義方法設定修飾符
		ctMethod.setModifiers(Modifier.PUBLIC);
		// 為自定義方法設定函式體
		StringBuffer buffer2 = new StringBuffer();
		buffer2.append("{\nSystem.out.println(\"begin!\");\n")
				.append("System.out.println(ename);\n")
				.append("System.out.println(eno);\n")
				.append("System.out.println(\"over!\");\n").append("}");
		ctMethod.setBody(buffer2.toString());
		ctClass.addMethod(ctMethod);

		//最好生成一個class
		Class<?> clazz = ctClass.toClass();
		Object obj = clazz.newInstance();
		//反射 執行方法
		obj.getClass().getMethod("printInfo", new Class[] {})
				.invoke(obj, new Object[] {});

		// 把生成的class檔案寫入檔案
		byte[] byteArr = ctClass.toBytecode();
		FileOutputStream fos = new FileOutputStream(new File("D://Emp.class"));
		fos.write(byteArr);
		fos.close();
	}
}
package com.soa.other.compiler;
public class Emp {  
	  
    private String ename;  
    private int eno;  
      
    public Emp(){  
        ename="yy";  
        eno=001;  
    }  
  
    public String getEname() {  
        return ename;  
    }  
  
    public void setEname(String ename) {  
        this.ename = ename;  
    }  
  
    public int getEno() {  
        return eno;  
    }  
  
    public void setEno(int eno) {  
        this.eno = eno;  
    }  
      
      
    //新增一個自定義方法  
    public void printInfo(){  
        System.out.println("begin!");  
        System.out.println(ename);  
        System.out.println(eno);  
        System.out.println("over!");  
    }  
}  

執行結果: 

 

Dubbo如何用Javassist實現動態代理

package com.alibaba.dubbo.common.compiler;


import com.alibaba.dubbo.common.extension.SPI;
 
@SPI("javassist")
public interface Compiler {

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

}

從介面定義程式碼我們可以看到dubbo通過SPI機制使用了Javasist

SPI機制,(在java.util.ServiceLoader裡有比較詳細的介紹)簡單來說就是為某個介面尋找服務實現的機制

動態編譯實現的類圖
package com.alibaba.dubbo.common.compiler.support;

import com.alibaba.dubbo.common.utils.ClassHelper;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtNewConstructor;
import javassist.CtNewMethod;
import javassist.LoaderClassPath;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * JavassistCompiler. (SPI, Singleton, ThreadSafe)
 *
 * @author william.liangf
 */
public class JavassistCompiler extends AbstractCompiler {

    private static final Pattern IMPORT_PATTERN = Pattern.compile("import\\s+([\\w\\.\\*]+);\n");

    private static final Pattern EXTENDS_PATTERN = Pattern.compile("\\s+extends\\s+([\\w\\.]+)[^\\{]*\\{\n");

    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]+;");

    @Override
    public Class<?> doCompile(String name, String source) throws Throwable {
        int i = name.lastIndexOf('.');
        String className = i < 0 ? name : name.substring(i + 1);
        ClassPool pool = new ClassPool(true);
        pool.appendClassPath(new LoaderClassPath(ClassHelper.getCallerClassLoader(getClass())));
        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);
                }
            }
        }
        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 = 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.addInterface(pool.get(ifaceClass));
            }
        }
        String body = source.substring(source.indexOf("{") + 1, source.length() - 1);
        String[] methods = METHODS_PATTERN.split(body);
        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));
                }
            }
        }
        return cls.toClass(ClassHelper.getCallerClassLoader(getClass()), JavassistCompiler.class.getProtectionDomain());
    }

}

為什麼沒有使用CGLib,而使用Javassist

這裡引用網上的一個測試結論:

1. ASM和JAVAASSIST位元組碼生成方式不相上下,都很快,是CGLIB的5倍。 
2. CGLIB次之,是JDK自帶的兩倍。 
3. JDK自帶的再次之,因JDK1.6對動態代理做了優化,如果用低版本JDK更慢,要注意的是JDK也是通過位元組碼生成來實現動態代理的,而不是反射。 
4. JAVAASSIST提供者動態代理介面最慢,比JDK自帶的還慢

最終選型: Javassist

雖然ASM稍快,但並沒有快一個數量級, 
而JAVAASSIST的位元組碼生成方式比ASM方便, 
JAVAASSIST只需用字串拼接出Java原始碼,便可生成相應位元組碼, 
而ASM需要手工寫位元組碼。 

 

怎麼配置動態代理方式

<dubbo:provider proxy="jdk" />或<dubbo:consumer proxy="jdk" />  預設情況下使用Javassist來進行動態代理

相關文章