| 
 RpcRequest 和 RpcResponseSocket傳輸Netty 傳輸同步與異步 阻塞與非阻塞總結(jié)
 
 提到 JAVA 中的動態(tài)代理,大多數(shù)人都不會對 JDK 動態(tài)代理感到陌生,Proxy,InvocationHandler 等類都是 J2SE 中的基礎(chǔ)概念。動態(tài)代理發(fā)生在服務(wù)調(diào)用方/客戶端,RPC 框架需要解決的一個問題是:像調(diào)用本地接口一樣調(diào)用遠(yuǎn)程的接口。于是如何組裝數(shù)據(jù)報文,經(jīng)過網(wǎng)絡(luò)傳輸發(fā)送至服務(wù)提供方,屏蔽遠(yuǎn)程接口調(diào)用的細(xì)節(jié),便是動態(tài)代理需要做的工作了。RPC 框架中的代理層往往是單獨的一層,以方便替換代理方式(如 motan 代理層位于com.weibo.api.motan.proxy,dubbo代理層位于com.alibaba.dubbo.common.bytecode)。 實現(xiàn)動態(tài)代理的方案有下列幾種: jdk 動態(tài)代理cglib 動態(tài)代理javassist 動態(tài)代理ASM 字節(jié)碼javassist 字節(jié)碼
 其中 cglib 底層實現(xiàn)依賴于 ASM,javassist 自成一派。由于 ASM 和 javassist 需要程序員直接操作字節(jié)碼,導(dǎo)致使用門檻相對較高,但實際上他們的應(yīng)用是非常廣泛的,如 Hibernate 底層使用了 javassist(默認(rèn))和 cglib,Spring 使用了 cglib 和 jdk 動態(tài)代理。 RPC 框架無論選擇何種代理技術(shù),所需要完成的任務(wù)其實是固定的,不外乎‘整理報文’,‘確認(rèn)網(wǎng)絡(luò)位置’,‘序列化’,’網(wǎng)絡(luò)傳輸’,‘反序列化’,’返回結(jié)果’… 技術(shù)選型的影響因素框架中使用何種動態(tài)代理技術(shù),影響因素也不少。 性能從早期 dubbo 的作者梁飛的博客 http://javatar./blog/814426 中可以得知 dubbo 選擇使用 javassist 作為動態(tài)代理方案主要考慮的因素是性能。 從其博客的測試結(jié)果來看 javassist > cglib > jdk 。但實際上他的測試過程稍微有點瑕疵:在 cglib 和 jdk 代理對象調(diào)用時,走的是反射調(diào)用,而在 javassist 生成的代理對象調(diào)用時,走的是直接調(diào)用(可以先閱讀下梁飛大大的博客)。這意味著 cglib 和 jdk 慢的原因并不是由動態(tài)代理產(chǎn)生的,而是由反射調(diào)用產(chǎn)生的(順帶一提,很多人認(rèn)為 jdk 動態(tài)代理的原理是反射,其實它的底層也是使用的字節(jié)碼技術(shù))。而最終我的測試結(jié)果,結(jié)論如下: javassist ≈ cglib > jdk 。javassist 和 cglib 的效率基本持平 ,而他們兩者的執(zhí)行效率基本可以達(dá)到 jdk 動態(tài)代理的2倍(這取決于測試的機器以及 jdk 的版本,jdk1.8 相較于 jdk1.6 動態(tài)代理技術(shù)有了質(zhì)的提升,所以并不是傳聞中的那樣:cglib 比 jdk 快 10倍)。文末會給出我的測試代碼。 依賴motan默認(rèn)的實現(xiàn)是jdk動態(tài)代理,代理方案支持SPI擴(kuò)展,可以自行擴(kuò)展其他實現(xiàn)方式。 使用jdk做為默認(rèn),主要是減少core包依賴,性能不是唯一考慮因素。另外使用字節(jié)碼方式j(luò)avaassist性能比較優(yōu)秀,動態(tài)代理模式下jdk性能也不會差多少。 – rayzhang0603(motan貢獻(xiàn)者)
 motan 選擇使用 jdk 動態(tài)代理,原因主要有兩個:減少 motan-core 的依賴,方便。至于擴(kuò)展性,dubbo 并沒有預(yù)留出動態(tài)代理的擴(kuò)展接口,而是寫死了 bytecode ,這點上 motan 做的較好。 易用性從 dubbo 和 motan 的源碼中便可以直觀的看出兩者的差距了,dubbo 為了使用 javassist 技術(shù)花費不少的精力,而 motan 使用 jdk 動態(tài)代理只用了一個類。dubbo 的設(shè)計者為了追求極致的性能而做出的工作是值得肯定的,motan 也預(yù)留了擴(kuò)展機制,兩者各有千秋。 動態(tài)代理入門指南為了方便對比幾種動態(tài)代理技術(shù),先準(zhǔn)備一個統(tǒng)一接口。 public interface BookApi {void sell();
 }
 
 JDK動態(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>1>
 if(method.getName().equals('sell')){
 System.out.print('');
 }
 return null;
 //            return method.invoke(delegate, objects);
 }
 
 <1> 在真正的 RPC 調(diào)用中 ,需要填充‘整理報文’,‘確認(rèn)網(wǎng)絡(luò)位置’,‘序列化’,’網(wǎng)絡(luò)傳輸’,‘反序列化’,’返回結(jié)果’等邏輯。 Cglib動態(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 動態(tài)代理的操作步驟沒有太大的區(qū)別,只不過是替換了 cglib 的API而已。 需要引入 cglib 依賴: dependency>groupId>cglibgroupId>
 artifactId>cglibartifactId>
 version>3.2.5version>
 dependency>
 
 Javassist字節(jié)碼到了 javassist,稍微有點不同了。因為它是通過直接操作字節(jié)碼來生成代理對象。 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.javassistgroupId>
 artifactId>javassistartifactId>
 version>3.21.0-GAversion>
 dependency>
 
 動態(tài)代理測試測試環(huán)境:window i5 8g jdk1.8 cglib3.2.5 javassist3.21.0-GA 動態(tài)代理其實分成了兩步:代理對象的創(chuàng)建,代理對象的調(diào)用。坊間流傳的動態(tài)代理性能對比主要指的是后者;前者一般不被大家考慮,如果遠(yuǎn)程Refer的對象是單例的,其只會被創(chuàng)建一次,而如果是原型模式,多例對象的創(chuàng)建其實也是性能損耗的一個考慮因素(只不過遠(yuǎn)沒有調(diào)用占比大)。 Create JDK Proxy: 21 ms Create CGLIB Proxy: 342 ms Create Javassist Bytecode Proxy: 419 ms
 可能出乎大家的意料,JDK 創(chuàng)建動態(tài)代理的速度比后兩者要快10倍左右。 下面是調(diào)用速度的測試: 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í)行速度一定會慢于 Cglib 和 Javassist,但最慢也就2倍,并沒有達(dá)到數(shù)量級的差距;Cglib 和 Javassist不相上下,差距不大(測試中偶爾發(fā)現(xiàn)Cglib實行速度會比平時慢10倍,不清楚是什么原因) 所以出于易用性和性能,私以為使用 Cglib 是一個很好的選擇(性能和 Javassist 持平,易用性和 Jdk 持平)。 反射調(diào)用既然提到了動態(tài)代理和 cglib ,順帶提一下反射調(diào)用如何加速的問題。RPC 框架中在 Provider 服務(wù)端需要根據(jù)客戶端傳遞來的 className + method + param 來找到容器中的實際方法執(zhí)行反射調(diào)用。除了反射調(diào)用外,還可以使用 Cglib 來加速。 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[]{});
 
 但實測效果發(fā)現(xiàn) Cglib 并不一定比 JDK 反射執(zhí)行速度快,還會跟具體的方法實現(xiàn)有關(guān)(大霧)。 測試代碼略長… 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;
 }
 }
 
 666. 彩蛋如果你對 RPC 并發(fā)感興趣,歡迎加入我的知識一起交流。知識星球 
 目前在知識星球(https://t./2VbiaEu)更新了如下 Dubbo 源碼解析如下:
 01. 調(diào)試環(huán)境搭建
 02. 項目結(jié)構(gòu)一覽
 03. API 配置(一)之應(yīng)用
 04. API 配置(二)之服務(wù)提供者
 05. API 配置(三)之服務(wù)消費者
 06. 屬性配置
 07. XML 配置
 08. 核心流程一覽
 ...
 一共 60 篇++
 1>
 |