從動態代理到Spring AOP(上)
一.前言
雖然平時日常開發很少用到動態代理,但是動態代理在底層框架等有著非常重要的意義。比如Spring AOP使用cglib和JDK動態代理,Hibernate底層使用了javassit和cglib動態代理,Dubbo使用javassist位元組碼(具體可以看Dubbo SPI)。
本文主要介紹什麼是動態代理及原理,下文將介紹Spring AOP
我們先思考一個問題:如何統計一個類各個方法的執行時間?可能你心裡有好多答案都可以解決問題。
那麼如果是這個專案的多個不同類呢?可能心裡也有答案,但是程式碼改動量不少。那麼有什麼其他的方法麼?
這時候動態代理就出來了,它可以靈活的在方法、程式碼點上切入我們想要實現的邏輯。如圖示:
二.體驗動態代理
2.1 JDK動態代理
在Java的java.lang.reflect包下提供了Proxy類和InvocationHandler介面,通過使用這兩個類可以生成JDK動態代理類或者JDK動態代理物件
JDK動態代理只能針對實現了介面的類進行拓展,我們還是就上面的問題來結局。所以這裡我們先建立一個介面,叫Developer(開發),裡面有個方法是develop方法。有RookieDeveloper(新生開發)、PrimaryDeveloper(初級開發)和AdvancedDeveloper(高階開發)實現其介面。類圖如下:
我現在對AdvancedDeveloper進行動態代理,先來看一下AdvancedDeveloper的程式碼:
接下來請看如何使用Proxy和InvocationHandler生成動態代理的:
執行結果如下:
2.2 CGLib動態代理
CGLIB代理的核心是net.sf.cglib.proxy.Enhancer類。我們可以將自定義的net.sf.cglib.proxy.MethodInterceptor實現類來得到強大的代理。代理的所有方法呼叫都會被分派給net.sf.cglib.proxy.MethodInterceptor的intercept方法。intercept方法然後呼叫底層物件。
我們看一下Cglib動態代理的例子,先看下PrimaryDeveloper類:
再看下CGLib動態代理測試類:
簡而言之,proxy.invoke方法呼叫的物件不是代理後的子類,proxy.invokeSuper方法呼叫的物件是代理後的子類(已增強),所以會再走一遍 MyMethodInterceptor的 interceptor方法,如果是個攔截器鏈條,就會重新在走一次攔截器鏈;最後看一下執行結果:
三.動態代理原理
3.1 JDK動態代理
我們首先看一下java.lang.reflect.Proxy#newProxyInstance這個方法:
這裡還有一個關鍵點,在java.lang.reflect.Proxy.ProxyClassFactory#apply方法裡,有一段程式碼生產對應的class位元組碼檔案:
簡單總結一下上面的程式碼:
- 生成一個實現interfaces所有介面且繼承Proxy類的代理類
- 使用Proxy(InvocationHandler h)構造一個代理類例項
- 傳入我們定義的InvocationHandler(例子中是匿名內部類),構造器例項化了代理物件
最後我們看一下生成的類的程式碼,我使用的是Bytecode Viewer,github地址:https://github.com/Konloch/bytecode-viewer。我們用debug evaluate獲取到代理類的class檔案,然後用Bytecode Viewer瞅瞅是啥樣子:
生產類的程式碼出來啦,繼承Proxy類,實現Developer介面,呼叫所有方法都轉換成了實際呼叫InvocationHandler介面的invoke方法:
3.2 CGLib動態代理
我們從生成的動態代理類長啥樣開始研究。上面的例子,新增System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/miaojiaxing/Downloads");後執行會生成幾個.class檔案:
- PrimaryDeveloper$$EnhancerByCGLIB$$bda772e0:CGLib生成的代理類
- PrimaryDeveloper$$EnhancerByCGLIB$$bda772e0$$FastClassByCGLIB$$aeaf1210:代理類的FastClass
- PrimaryDeveloper$$FastClassByCGLIB$$de1a7774:被代理類的FastClass(有點繞口)
首先用反編譯工具檢視一下PrimaryDeveloper$$EnhancerByCGLIB$$bda772e0這個類的程式碼:
1 package com.mjx.java.proxy; 2 3 import java.lang.reflect.Method; 4 import net.sf.cglib.core.ReflectUtils; 5 import net.sf.cglib.core.Signature; 6 import net.sf.cglib.proxy.Callback; 7 import net.sf.cglib.proxy.Factory; 8 import net.sf.cglib.proxy.MethodInterceptor; 9 import net.sf.cglib.proxy.MethodProxy; 10 11 public class PrimaryDeveloper$$EnhancerByCGLIB$$bda772e0 extends PrimaryDeveloper implements Factory { 12 private boolean CGLIB$BOUND; 13 public static Object CGLIB$FACTORY_DATA; 14 private static final ThreadLocal CGLIB$THREAD_CALLBACKS; 15 private static final Callback[] CGLIB$STATIC_CALLBACKS; 16 private MethodInterceptor CGLIB$CALLBACK_0; 17 private static Object CGLIB$CALLBACK_FILTER; 18 19 // 代理類會獲得所有在父類繼承來的方法,並且會有MethodProxy與之對應 20 private static final Method CGLIB$laugh$0$Method; 21 private static final MethodProxy CGLIB$laugh$0$Proxy; 22 private static final Object[] CGLIB$emptyArgs; 23 24 // 代理類會獲得所有在父類繼承來的方法,並且會有MethodProxy與之對應 25 private static final Method CGLIB$develop$1$Method; 26 private static final MethodProxy CGLIB$develop$1$Proxy; 27 28 private static final Method CGLIB$say$2$Method; 29 private static final MethodProxy CGLIB$say$2$Proxy; 30 private static final Method CGLIB$equals$3$Method; 31 private static final MethodProxy CGLIB$equals$3$Proxy; 32 private static final Method CGLIB$toString$4$Method; 33 private static final MethodProxy CGLIB$toString$4$Proxy; 34 private static final Method CGLIB$hashCode$5$Method; 35 private static final MethodProxy CGLIB$hashCode$5$Proxy; 36 private static final Method CGLIB$clone$6$Method; 37 private static final MethodProxy CGLIB$clone$6$Proxy; 38 39 static void CGLIB$STATICHOOK1() { 40 CGLIB$THREAD_CALLBACKS = new ThreadLocal(); 41 CGLIB$emptyArgs = new Object[0]; 42 Class var0 = Class.forName("com.mjx.java.proxy.PrimaryDeveloper$$EnhancerByCGLIB$$bda772e0"); 43 Class var1; 44 Method[] var10000 = ReflectUtils.findMethods(new String[]{"laugh", "()V", "develop", "()V", "say", "()V"}, (var1 = Class.forName("com.mjx.java.proxy.PrimaryDeveloper")).getDeclaredMethods()); 45 CGLIB$laugh$0$Method = var10000[0]; 46 CGLIB$laugh$0$Proxy = MethodProxy.create(var1, var0, "()V", "laugh", "CGLIB$laugh$0"); 47 CGLIB$develop$1$Method = var10000[1]; 48 CGLIB$develop$1$Proxy = MethodProxy.create(var1, var0, "()V", "develop", "CGLIB$develop$1"); 49 CGLIB$say$2$Method = var10000[2]; 50 CGLIB$say$2$Proxy = MethodProxy.create(var1, var0, "()V", "say", "CGLIB$say$2"); 51 var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods()); 52 CGLIB$equals$3$Method = var10000[0]; 53 CGLIB$equals$3$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$3"); 54 CGLIB$toString$4$Method = var10000[1]; 55 CGLIB$toString$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$4"); 56 CGLIB$hashCode$5$Method = var10000[2]; 57 CGLIB$hashCode$5$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$5"); 58 CGLIB$clone$6$Method = var10000[3]; 59 CGLIB$clone$6$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$6"); 60 } 61 62 final void CGLIB$laugh$0() { 63 super.laugh(); 64 } 65 66 public final void laugh() { 67 MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; 68 if (var10000 == null) { 69 CGLIB$BIND_CALLBACKS(this); 70 var10000 = this.CGLIB$CALLBACK_0; 71 } 72 73 if (var10000 != null) { 74 // 攔截器 75 var10000.intercept(this, CGLIB$laugh$0$Method, CGLIB$emptyArgs, CGLIB$laugh$0$Proxy); 76 } else { 77 super.laugh(); 78 } 79 } 80 81 // methodProxy.invokeSuper會呼叫 82 final void CGLIB$develop$1() throws Exception { 83 super.develop(); 84 } 85 86 public final void develop() throws Exception { 87 MethodInterceptor var10000 = this.CGLIB$CALLBACK_0; 88 if (var10000 == null) { 89 CGLIB$BIND_CALLBACKS(this); 90 var10000 = this.CGLIB$CALLBACK_0; 91 } 92 93 // 攔截 94 if (var10000 != null) { 95 var10000.intercept(this, CGLIB$develop$1$Method, CGLIB$emptyArgs, CGLIB$develop$1$Proxy); 96 } else { 97 super.develop(); 98 } 99 }
我們可以看到代理類會獲得所有在父類繼承來的方法,並且會有MethodProxy與之對應,這個非常關鍵,上面已經用紅色標註。
3.2.1 MethodProxy
CGLIB$develop$1$Proxy = MethodProxy.create(var1, var0, "()V", "develop", "CGLIB$develop$1");
我們先看下建立MethodProxy:
1 public class MethodProxy { 2 private Signature sig1; 3 private Signature sig2; 4 private CreateInfo createInfo; 5 6 private final Object initLock = new Object(); 7 private volatile FastClassInfo fastClassInfo; 8 9 /** 10 * For internal use by {@link Enhancer} only; see the {@link net.sf.cglib.reflect.FastMethod} class 11 * for similar functionality. 12 */ 13 // c1:被代理物件Class 14 // c2:代理物件Class 15 // desc:入參型別 16 // name1:被代理方法名 17 // name2:代理方法名 18 public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) { 19 MethodProxy proxy = new MethodProxy(); 20 proxy.sig1 = new Signature(name1, desc); 21 proxy.sig2 = new Signature(name2, desc); 22 proxy.createInfo = new CreateInfo(c1, c2); 23 return proxy; 24 } 25 26 private static class CreateInfo 27 { 28 Class c1; 29 Class c2; 30 NamingPolicy namingPolicy; 31 GeneratorStrategy strategy; 32 boolean attemptLoad; 33 34 public CreateInfo(Class c1, Class c2) 35 { 36 this.c1 = c1; 37 this.c2 = c2; 38 AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent(); 39 if (fromEnhancer != null) { 40 namingPolicy = fromEnhancer.getNamingPolicy(); 41 strategy = fromEnhancer.getStrategy(); 42 attemptLoad = fromEnhancer.getAttemptLoad(); 43 } 44 } 45 } 46 }
建立代理之後,執行方法這裡走到紅色的程式碼:var10000.intercept(this, CGLIB$develop$1$Method, CGLIB$emptyArgs, CGLIB$develop$1$Proxy);走到了我們寫的CglibInterceptor類的intercept方法,裡面呼叫了proxy.invokeSuper(obj,args);
1 public Object invokeSuper(Object obj, Object[] args) throws Throwable { 2 try { 3 init(); 4 FastClassInfo fci = fastClassInfo; 5 // 這裡的f2就是PrimaryDeveloper$$EnhancerByCGLIB$$bda772e0$$FastClassByCGLIB$$aeaf1210 6 return fci.f2.invoke(fci.i2, obj, args); 7 } catch (InvocationTargetException e) { 8 throw e.getTargetException(); 9 } 10 } 11 12 private static class FastClassInfo{ 13 FastClass f1;//被代理類FastClass,這裡就是PrimaryDeveloper$$FastClassByCGLIB$$de1a7774 14 FastClass f2;//代理類FastClass,這裡就是PrimaryDeveloper$$EnhancerByCGLIB$$bda772e0$$FastClassByCGLIB$$aeaf1210 15 int i1;//被代理方法index 16 int i2;//代理方法index 17 }
3.2.1 FastClass機制
Cglib動態代理執行代理方法效率之所以比JDK的高是因為Cglib採用了FastClass機制,它為代理類和被代理類各生成一個Class(就是上面的f1和f2),它會為代理類或被代理類的方法分配一個index(int型別)。這個index是用簽名的hashCode來計算出來的Index(下面程式碼有),FastClass就可以直接定位要呼叫的方法直接進行呼叫,那麼就省去了反射,所以呼叫效率比JDK動態代理通過反射呼叫高。f2我們已經知道是PrimaryDeveloper$$EnhancerByCGLIB$$bda772e0$$FastClassByCGLIB$$aeaf1210,下面我們反編譯一下f2看看:
繼續看下f2的invoke方法,直接呼叫PrimaryDeveloper$$EnhancerByCGLIB$$bda772e0的方法,無需反射。
最後我們整理一下呼叫過程,這樣比較清晰了吧,如圖:
四.總結
JDK動態代理:
- 首先這個類要實現了某介面
- 其核心就是克隆interfaces的所有介面,繼承Proxy類,生成一個心類作為代理類,這個類裡有我們定義的實現InvocationHandler介面的類進行代理邏輯處理
CGLib動態代理:
- JDK動態代理有個重大缺陷,必須要實現接口才可以使用,而CGLib動態代理只要有個類就行,動態生成子類。如果是private方法,final方法等描述的方法是不能被代理的
- Cglib動態代理執行代理方法效率之所以比JDK的高是因為Cglib採用了FastClass機制