從動態代理到Spring AOP(上)

GrimMjx發表於2019-07-18

 一.前言

  雖然平時日常開發很少用到動態代理,但是動態代理在底層框架等有著非常重要的意義。比如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位元組碼檔案:

  簡單總結一下上面的程式碼:

  1. 生成一個實現interfaces所有介面且繼承Proxy類的代理類
  2. 使用Proxy(InvocationHandler h)構造一個代理類例項
  3. 傳入我們定義的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機制

相關文章