提到 JAVA 中的動(dòng)態(tài)代理耙旦,大多數(shù)人都不會(huì)對(duì) JDK 動(dòng)態(tài)代理感到陌生,Proxy萝究,InvocationHandler 等類都是 J2SE 中的基礎(chǔ)概念免都。動(dòng)態(tài)代理發(fā)生在服務(wù)調(diào)用方/客戶端,RPC 框架需要解決的一個(gè)問(wèn)題是:像調(diào)用本地接口一樣調(diào)用遠(yuǎn)程的接口帆竹。于是如何組裝數(shù)據(jù)報(bào)文绕娘,經(jīng)過(guò)網(wǎng)絡(luò)傳輸發(fā)送至服務(wù)提供方,屏蔽遠(yuǎn)程接口調(diào)用的細(xì)節(jié)栽连,便是動(dòng)態(tài)代理需要做的工作了险领。RPC 框架中的代理層往往是單獨(dú)的一層,以方便替換代理方式(如 motan 代理層位于com.weibo.api.motan.proxy
秒紧,dubbo代理層位于 com.alibaba.dubbo.common.bytecode
)绢陌。
實(shí)現(xiàn)動(dòng)態(tài)代理的方案有下列幾種:
- jdk 動(dòng)態(tài)代理
- cglib 動(dòng)態(tài)代理
- javassist 動(dòng)態(tài)代理
- ASM 字節(jié)碼
- javassist 字節(jié)碼
其中 cglib 底層實(shí)現(xiàn)依賴于 ASM,javassist 自成一派熔恢。由于 ASM 和 javassist 需要程序員直接操作字節(jié)碼脐湾,導(dǎo)致使用門檻相對(duì)較高,但實(shí)際上他們的應(yīng)用是非常廣泛的叙淌,如 Hibernate 底層使用了 javassist(默認(rèn))和 cglib秤掌,Spring 使用了 cglib 和 jdk 動(dòng)態(tài)代理。
RPC 框架無(wú)論選擇何種代理技術(shù)鹰霍,所需要完成的任務(wù)其實(shí)是固定的闻鉴,不外乎‘整理報(bào)文’,‘確認(rèn)網(wǎng)絡(luò)位置’茂洒,‘序列化’,'網(wǎng)絡(luò)傳輸'孟岛,‘反序列化’,'返回結(jié)果'…
技術(shù)選型的影響因素
框架中使用何種動(dòng)態(tài)代理技術(shù)获黔,影響因素也不少蚀苛。
性能
從早期 dubbo 的作者梁飛的博客 http://javatar.iteye.com/blog/814426 中可以得知 dubbo 選擇使用 javassist 作為動(dòng)態(tài)代理方案主要考慮的因素是性能。
從其博客的測(cè)試結(jié)果來(lái)看 javassist > cglib > jdk 玷氏。但實(shí)際上他的測(cè)試過(guò)程稍微有點(diǎn)瑕疵:在 cglib 和 jdk 代理對(duì)象調(diào)用時(shí)堵未,走的是反射調(diào)用,而在 javassist 生成的代理對(duì)象調(diào)用時(shí)盏触,走的是直接調(diào)用(可以先閱讀下梁飛大大的博客)渗蟹。這意味著 cglib 和 jdk 慢的原因并不是由動(dòng)態(tài)代理產(chǎn)生的块饺,而是由反射調(diào)用產(chǎn)生的(順帶一提,很多人認(rèn)為 jdk 動(dòng)態(tài)代理的原理是反射雌芽,其實(shí)它的底層也是使用的字節(jié)碼技術(shù))授艰。而最終我的測(cè)試結(jié)果,結(jié)論如下: javassist ≈ cglib > jdk 世落。javassist 和 cglib 的效率基本持平 淮腾,而他們兩者的執(zhí)行效率基本可以達(dá)到 jdk 動(dòng)態(tài)代理的2倍(這取決于測(cè)試的機(jī)器以及 jdk 的版本,jdk1.8 相較于 jdk1.6 動(dòng)態(tài)代理技術(shù)有了質(zhì)的提升屉佳,所以并不是傳聞中的那樣:cglib 比 jdk 快 10倍)谷朝。文末會(huì)給出我的測(cè)試代碼。
依賴
motan默認(rèn)的實(shí)現(xiàn)是jdk動(dòng)態(tài)代理武花,代理方案支持SPI擴(kuò)展圆凰,可以自行擴(kuò)展其他實(shí)現(xiàn)方式。
使用jdk做為默認(rèn)体箕,主要是減少core包依賴专钉,性能不是唯一考慮因素。另外使用字節(jié)碼方式j(luò)avaassist性能比較優(yōu)秀累铅,動(dòng)態(tài)代理模式下jdk性能也不會(huì)差多少跃须。
-- rayzhang0603(motan貢獻(xiàn)者)
motan 選擇使用 jdk 動(dòng)態(tài)代理,原因主要有兩個(gè):減少 motan-core 的依賴争群,方便回怜。至于擴(kuò)展性大年,dubbo 并沒(méi)有預(yù)留出動(dòng)態(tài)代理的擴(kuò)展接口换薄,而是寫死了 bytecode ,這點(diǎn)上 motan 做的較好翔试。
易用性
從 dubbo 和 motan 的源碼中便可以直觀的看出兩者的差距了轻要,dubbo 為了使用 javassist 技術(shù)花費(fèi)不少的精力,而 motan 使用 jdk 動(dòng)態(tài)代理只用了一個(gè)類垦缅。dubbo 的設(shè)計(jì)者為了追求極致的性能而做出的工作是值得肯定的冲泥,motan 也預(yù)留了擴(kuò)展機(jī)制,兩者各有千秋壁涎。
動(dòng)態(tài)代理入門指南
為了方便對(duì)比幾種動(dòng)態(tài)代理技術(shù)凡恍,先準(zhǔn)備一個(gè)統(tǒng)一接口。
public interface BookApi {
void sell();
}
JDK動(dòng)態(tài)代理
private static BookApi createJdkDynamicProxy(final BookApi delegate) {
BookApi jdkProxy = (BookApi) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{BookApi.class}, new JdkHandler(delegate));
return jdkProxy;
}
private static class JdkHandler implements InvocationHandler {
final Object delegate;
JdkHandler(Object delegate) {
this.delegate = delegate;
}
@Override
public Object invoke(Object object, Method method, Object[] objects)
throws Throwable {
//添加代理邏輯<1>
if(method.getName().equals("sell")){
System.out.print("");
}
return null;
// return method.invoke(delegate, objects);
}
<1> 在真正的 RPC 調(diào)用中 怔球,需要填充‘整理報(bào)文’嚼酝,‘確認(rèn)網(wǎng)絡(luò)位置’,‘序列化’,'網(wǎng)絡(luò)傳輸'竟坛,‘反序列化’闽巩,'返回結(jié)果'等邏輯钧舌。
Cglib動(dòng)態(tài)代理
private static BookApi createCglibDynamicProxy(final BookApi delegate) throws Exception {
Enhancer enhancer = new Enhancer();
enhancer.setCallback(new CglibInterceptor(delegate));
enhancer.setInterfaces(new Class[]{BookApi.class});
BookApi cglibProxy = (BookApi) enhancer.create();
return cglibProxy;
}
private static class CglibInterceptor implements MethodInterceptor {
final Object delegate;
CglibInterceptor(Object delegate) {
this.delegate = delegate;
}
@Override
public Object intercept(Object object, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
//添加代理邏輯
if(method.getName().equals("sell")) {
System.out.print("");
}
return null;
// return methodProxy.invoke(delegate, objects);
}
}
和 JDK 動(dòng)態(tài)代理的操作步驟沒(méi)有太大的區(qū)別,只不過(guò)是替換了 cglib 的API而已涎跨。
需要引入 cglib 依賴:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
Javassist字節(jié)碼
到了 javassist洼冻,稍微有點(diǎn)不同了。因?yàn)樗峭ㄟ^(guò)直接操作字節(jié)碼來(lái)生成代理對(duì)象隅很。
private static BookApi createJavassistBytecodeDynamicProxy() throws Exception {
ClassPool mPool = new ClassPool(true);
CtClass mCtc = mPool.makeClass(BookApi.class.getName() + "JavaassistProxy");
mCtc.addInterface(mPool.get(BookApi.class.getName()));
mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));
mCtc.addMethod(CtNewMethod.make(
"public void sell() { System.out.print(\"\") ; }", mCtc));
Class<?> pc = mCtc.toClass();
BookApi bytecodeProxy = (BookApi) pc.newInstance();
return bytecodeProxy;
}
需要引入 javassist 依賴:
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.21.0-GA</version>
</dependency>
動(dòng)態(tài)代理測(cè)試
測(cè)試環(huán)境:window i5 8g jdk1.8 cglib3.2.5 javassist3.21.0-GA
動(dòng)態(tài)代理其實(shí)分成了兩步:代理對(duì)象的創(chuàng)建撞牢,代理對(duì)象的調(diào)用。坊間流傳的動(dòng)態(tài)代理性能對(duì)比主要指的是后者叔营;前者一般不被大家考慮普泡,如果遠(yuǎn)程Refer的對(duì)象是單例的,其只會(huì)被創(chuàng)建一次审编,而如果是原型模式撼班,多例對(duì)象的創(chuàng)建其實(shí)也是性能損耗的一個(gè)考慮因素(只不過(guò)遠(yuǎn)沒(méi)有調(diào)用占比大)。
Create JDK Proxy: 21 ms
Create CGLIB Proxy: 342 ms
Create Javassist Bytecode Proxy: 419 ms
可能出乎大家的意料垒酬,JDK 創(chuàng)建動(dòng)態(tài)代理的速度比后兩者要快10倍左右砰嘁。
下面是調(diào)用速度的測(cè)試:
case 1:
JDK Proxy invoke cost 1912 ms
CGLIB Proxy invoke cost 1015 ms
JavassistBytecode Proxy invoke cost 1280 ms
case 2:
JDK Proxy invoke cost 1747 ms
CGLIB Proxy invoke cost 1234 ms
JavassistBytecode Proxy invoke cost 1175 ms
case 3:
JDK Proxy invoke cost 2616 ms
CGLIB Proxy invoke cost 1373 ms
JavassistBytecode Proxy invoke cost 1335 ms
Jdk 的執(zhí)行速度一定會(huì)慢于 Cglib 和 Javassist,但最慢也就2倍勘究,并沒(méi)有達(dá)到數(shù)量級(jí)的差距矮湘;Cglib 和 Javassist不相上下,差距不大(測(cè)試中偶爾發(fā)現(xiàn)Cglib實(shí)行速度會(huì)比平時(shí)慢10倍口糕,不清楚是什么原因)
所以出于易用性和性能缅阳,私以為使用 Cglib 是一個(gè)很好的選擇(性能和 Javassist 持平,易用性和 Jdk 持平)景描。
反射調(diào)用
既然提到了動(dòng)態(tài)代理和 cglib 十办,順帶提一下反射調(diào)用如何加速的問(wèn)題。RPC 框架中在 Provider 服務(wù)端需要根據(jù)客戶端傳遞來(lái)的 className + method + param 來(lái)找到容器中的實(shí)際方法執(zhí)行反射調(diào)用超棺。除了反射調(diào)用外向族,還可以使用 Cglib 來(lái)加速。
JDK反射調(diào)用
Method method = serviceClass.getMethod(methodName, new Class[]{});
method.invoke(delegate, new Object[]{});
Cglib調(diào)用
FastClass serviceFastClass = FastClass.create(serviceClass);
FastMethod serviceFastMethod = serviceFastClass.getMethod(methodName, new Class[]{});
serviceFastMethod.invoke(delegate, new Object[]{});
但實(shí)測(cè)效果發(fā)現(xiàn) Cglib 并不一定比 JDK 反射執(zhí)行速度快棠绘,還會(huì)跟具體的方法實(shí)現(xiàn)有關(guān)(大霧)件相。
測(cè)試代碼
略長(zhǎng)…
public class Main {
public static void main(String[] args) throws Exception {
BookApi delegate = new BookApiImpl();
long time = System.currentTimeMillis();
BookApi jdkProxy = createJdkDynamicProxy(delegate);
time = System.currentTimeMillis() - time;
System.out.println("Create JDK Proxy: " + time + " ms");
time = System.currentTimeMillis();
BookApi cglibProxy = createCglibDynamicProxy(delegate);
time = System.currentTimeMillis() - time;
System.out.println("Create CGLIB Proxy: " + time + " ms");
time = System.currentTimeMillis();
BookApi javassistBytecodeProxy = createJavassistBytecodeDynamicProxy();
time = System.currentTimeMillis() - time;
System.out.println("Create JavassistBytecode Proxy: " + time + " ms");
for (int i = 0; i < 10; i++) {
jdkProxy.sell();//warm
}
long start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
jdkProxy.sell();
}
System.out.println("JDK Proxy invoke cost " + (System.currentTimeMillis() - start) + " ms");
for (int i = 0; i < 10; i++) {
cglibProxy.sell();//warm
}
start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
cglibProxy.sell();
}
System.out.println("CGLIB Proxy invoke cost " + (System.currentTimeMillis() - start) + " ms");
for (int i = 0; i < 10; i++) {
javassistBytecodeProxy.sell();//warm
}
start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
javassistBytecodeProxy.sell();
}
System.out.println("JavassistBytecode Proxy invoke cost " + (System.currentTimeMillis() - start) + " ms");
Class<?> serviceClass = delegate.getClass();
String methodName = "sell";
for (int i = 0; i < 10; i++) {
cglibProxy.sell();//warm
}
// 執(zhí)行反射調(diào)用
for (int i = 0; i < 10; i++) {//warm
Method method = serviceClass.getMethod(methodName, new Class[]{});
method.invoke(delegate, new Object[]{});
}
start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
Method method = serviceClass.getMethod(methodName, new Class[]{});
method.invoke(delegate, new Object[]{});
}
System.out.println("反射 invoke cost " + (System.currentTimeMillis() - start) + " ms");
// 使用 CGLib 執(zhí)行反射調(diào)用
for (int i = 0; i < 10; i++) {//warm
FastClass serviceFastClass = FastClass.create(serviceClass);
FastMethod serviceFastMethod = serviceFastClass.getMethod(methodName, new Class[]{});
serviceFastMethod.invoke(delegate, new Object[]{});
}
start = System.currentTimeMillis();
for (int i = 0; i < 10000000; i++) {
FastClass serviceFastClass = FastClass.create(serviceClass);
FastMethod serviceFastMethod = serviceFastClass.getMethod(methodName, new Class[]{});
serviceFastMethod.invoke(delegate, new Object[]{});
}
System.out.println("CGLIB invoke cost " + (System.currentTimeMillis() - start) + " ms");
}
private static BookApi createJdkDynamicProxy(final BookApi delegate) {
BookApi jdkProxy = (BookApi) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
new Class[]{BookApi.class}, new JdkHandler(delegate));
return jdkProxy;
}
private static class JdkHandler implements InvocationHandler {
final Object delegate;
JdkHandler(Object delegate) {
this.delegate = delegate;
}
@Override
public Object invoke(Object object, Method method, Object[] objects)
throws Throwable {
//添加代理邏輯
if(method.getName().equals("sell")){
System.out.print("");
}
return null;
// return method.invoke(delegate, objects);
}
}
private static BookApi createCglibDynamicProxy(final BookApi delegate) throws Exception {
Enhancer enhancer = new Enhancer();
enhancer.setCallback(new CglibInterceptor(delegate));
enhancer.setInterfaces(new Class[]{BookApi.class});
BookApi cglibProxy = (BookApi) enhancer.create();
return cglibProxy;
}
private static class CglibInterceptor implements MethodInterceptor {
final Object delegate;
CglibInterceptor(Object delegate) {
this.delegate = delegate;
}
@Override
public Object intercept(Object object, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
//添加代理邏輯
if(method.getName().equals("sell")) {
System.out.print("");
}
return null;
// return methodProxy.invoke(delegate, objects);
}
}
private static BookApi createJavassistBytecodeDynamicProxy() throws Exception {
ClassPool mPool = new ClassPool(true);
CtClass mCtc = mPool.makeClass(BookApi.class.getName() + "JavaassistProxy");
mCtc.addInterface(mPool.get(BookApi.class.getName()));
mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));
mCtc.addMethod(CtNewMethod.make(
"public void sell() { System.out.print(\"\") ; }", mCtc));
Class<?> pc = mCtc.toClass();
BookApi bytecodeProxy = (BookApi) pc.newInstance();
return bytecodeProxy;
}
}