Dubbo原始碼之動態編譯
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來進行動態代理
相關文章
- Android FrameWork 之原始碼編譯AndroidFramework原始碼編譯
- Dubbo之SPI原始碼分析原始碼
- Dubbo原始碼解析之SPI原始碼
- apache動態編譯/靜態編譯區別Apache編譯
- Dubbo原始碼分析(六)Dubbo通訊的編碼解碼機制原始碼
- java動態編譯Java編譯
- Dubbo原始碼解讀-Dubbo的容器啟動原始碼
- Dubbo原始碼之服務引用原始碼
- Dubbo原始碼學習之-通過原始碼看看dubbo對netty的使用原始碼Netty
- 編譯lua動態庫編譯
- 動態編譯JAVA程式編譯Java
- dubbo原始碼解析之負載均衡原始碼負載
- Dubbo原始碼解析之SPI機制原始碼
- Dubbo之限流TpsLimitFilter原始碼分析MITFilter原始碼
- dubbo原始碼解析之基礎篇原始碼
- dubbo原始碼分析之叢集Cluster原始碼
- Dubbo原始碼分析之服務引用原始碼
- Dubbo原始碼分析之服務暴露原始碼
- dubbo原始碼系列之filter的前生原始碼Filter
- 【Dubbo原始碼閱讀系列】之 Dubbo XML 配置載入原始碼XML
- 原始碼編譯Vim 8原始碼編譯
- MongoDB(0)- 原始碼編譯MongoDB原始碼編譯
- 編譯Android原始碼編譯Android原始碼
- 編譯hibernate原始碼編譯原始碼
- Docker編譯Azerothcore原始碼Docker編譯原始碼
- Spring原始碼編譯Spring原始碼編譯
- 跟著大彬讀原始碼 - Redis 7 - 物件編碼之簡單動態字串原始碼Redis物件字串
- Mac 10.14.4 編譯openjdk1.9原始碼 及整合clion動態除錯Mac編譯JDK原始碼除錯
- Dubbo原始碼解析之服務叢集原始碼
- Dubbo原始碼解析之負載均衡策略原始碼負載
- Dubbo原始碼學習之-SPI介紹原始碼
- dubbo原始碼解析之ExtensionLoader類(二)原始碼
- Vue 原始碼解讀(8)—— 編譯器 之 解析(下)Vue原始碼編譯
- Vue 原始碼解讀(9)—— 編譯器 之 優化Vue原始碼編譯優化
- TiDB 原始碼系列之沉浸式編譯 TiDBTiDB原始碼編譯
- Vue 原始碼解讀(8)—— 編譯器 之 解析(上)Vue原始碼編譯
- CSP之壓縮編碼(動態規劃)動態規劃
- 從fdk_aac編碼器到自動靜態編譯FFmpeg編譯