Spring AOP高級——源碼實現(1)動態代理技術


jdk1.8.0_144  

  在正式進入Spring AOP的源碼實現前,我們需要准備一定的基礎也就是面向切面編程的核心——動態代理。 動態代理實際上也是一種結構型的設計模式,JDK中已經為我們准備好了這種設計模式,不過這種JDK為我們提供的動態代理有2個缺點:

  1. 只能代理實現了接口的目標對象;
  2. 基於反射,效率低

  鑒於以上2個缺點,於是就出現了第二種動態代理技術——CGLIB(Code Generation Library)。這種代理技術一是不需要目標對象實現接口(這大大擴展了使用范圍),二是它是基於字節碼實現(這比反射效率高)。當然它並不是完全沒有缺點,因為它不能代理final方法(因為它的動態代理實際是生成目標對象的子類)。

  Spring AOP中生成代理對象時既可以使用JDK的動態代理技術,也可以使用CGLIB的動態代理技術,本章首先對這兩者動態代理技術做簡要了解,便於后續源碼的理解。

JDK動態代理技術

  JDK動態代理技術首先要求我們目標對象需要實現一個接口:

1 package proxy;
2 
3 /**
4  * Created by Kevin on 2017/11/8.
5  */
6 public interface Subject {
7     void sayHello();
8 }

  接下來就是我們需要代理的真實對象,即目標對象:

 1 package proxy;
 2 
 3 /**
 4  * 目標對象,即需要被代理的對象
 5  * Created by Kevin on 2017/11/8.
 6  */
 7 public class RealSubject implements Subject{
 8     public void sayHello() {
 9         System.out.println("hello world");
10     }
11 }

  這是一個真實的對象,我們希望在不更改原有代碼邏輯的基礎上增強該類的sayHello方法,利用JDK動態代理技術需要我們實現InvocationHandler接口中的invoke方法:

 1 package proxy;
 2 
 3 import java.lang.reflect.InvocationHandler;
 4 import java.lang.reflect.Method;
 5 
 6 public class ProxySubject implements InvocationHandler {
 7     private Object target;
 8 
 9     public ProxySubject(Object target) {
10         this.target = target;
11     }
12 
13     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
14         System.out.println("調用前");
15         Object object = method.invoke(target, args);
16         System.out.println("調用后");
17         return object;
18     }
19 }

  第15行,在invoke方法中可以看到,在調用目標對象的方法前后我們對方法進行了增加,這其實就是AOP中Before和After通知的奧義所在。

  加入測試代碼:

 1 package proxy;
 2 
 3 import java.lang.reflect.Proxy;
 4 
 5 /**
 6  * Created by Kevin on 2017/11/8.
 7  */
 8 public class Test {
 9     public static void main(String[] args) {
10         Subject subject = (Subject) Proxy.newProxyInstance(RealSubject.class.getClassLoader(), RealSubject.class.getInterfaces(), new ProxySubject(new RealSubject()));
11         subject.sayHello();
12 
13         //查看subject對象的類型
14         System.out.println(subject.getClass().getName());
15     }
16 }

  執行結果:

  可以看到和AOP幾乎一樣,前面提到過,動態代理就是AOP的核心。同時我們可以看到被代理的類的類型是:com.sun.proxy.$Proxy0。等會深入JDK源碼時我們將會看到為什么。

  回到上面的例子,我們通過Proxy. newProxyInstance生成了一個代理類,顯然這個類是在Run-Time(運行時)生成的,也就是說,JDK動態代理中代理類的生成來自於Java反射機制的支撐。

  上面例子中我們將實現InvocationHandler的類取名為“ProxySubject”,這其實是不准確的,我們看到了最后代理類的類型並不是ProxySubject,這個類實際上是處理需要增強的方法,也就是在invoke中的實現邏輯,最后並不是生成這個類型的代理類,這也不是生成的代理類,所以取名這個是不准確的。

  首先從Proxy.newProxyInstance開始,來研究JDK是如何生成代理類的。

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

  該方法有3個參數,了解JVM類加載的可能知道確定為同一個類需要有2個條件:

  • 類的全限定名稱相同

  • 加載類的類加載器相同

  要想生成目標對象的代理首先就要確保其類加載器相同,所以需要將目標對象的類加載器作為參數傳遞;其次JDK動態代理技術需要代理類和目標對象都繼承自同一接口,所以需要將目標對象的接口作為參數傳遞;最后,傳遞InvocationHandler,這是主角,因為我們對目標對象的增強邏輯在這個實現類中,傳遞該對象使得代理類能夠對其進行調用。

  在Proxy.newProxyInstance方法中創建代理類的過程主要有3步:

1.檢查

 1 public static Object newProxyInstance(ClassLoader loader,
 2                                       Class<?>[] interfaces,
 3                                       InvocationHandler h)
 4     throws IllegalArgumentException
 5 {
 6     Objects.requireNonNull(h);    //1.1檢查參數是否為空
 7 
 8     final Class<?>[] intfs = interfaces.clone();
 9     final SecurityManager sm = System.getSecurityManager();    //獲取安全管理器,安全管理器用於對外部資源的訪問控制
10     if (sm != null) {
11         checkProxyAccess(Reflection.getCallerClass(), loader, intfs);    //1.2檢查是否有訪問權限
12     }

  在上面源碼中有一個獲取安全管理器以及檢查是否具有訪問權限的過程。安全管理器可能在實際中不太常用,它是為了程序在某些敏感資源的訪問上做的權限控制,也就是起到保護程序的作用。在這里暫時不用仔細去探究,只需要大概了解即可。這里做的權限檢查實際上是對ClassLoader的檢查,例如:有的程序不允許你對類進行代理,此時加入安全管理器即可防止你對該類的代理。

2.獲取代理類型

Class<?> cl = getProxyClass0(loader, intfs);    //獲取代理類類型

  這句話通過目標對象的類加載器,以及它所繼承的接口,即可獲取代理類的類型。

 1 /**
 2  * Generate a proxy class.  Must call the checkProxyAccess method
 3  * to perform permission checks before calling this.
 4 *從注釋中可以看到,這個方法用於生成代理類,在調用此方法前必須要確保
 5 *已經做過權限檢查。
 6  */
 7 private static Class<?> getProxyClass0(ClassLoader loader,
 8                                        Class<?>... interfaces) {
 9     if (interfaces.length > 65535) {        //一個類最多實現65535個接口
10         throw new IllegalArgumentException("interface limit exceeded");
11     }
12     return proxyClassCache.get(loader, interfaces);    //先從緩存中獲取代理類,如果不存在則通過ProxyClassFactory創建,這其中會涉及到比較復雜的代理緩存機制,本篇主要講動態代理過程的源碼實現,對於動態代理的緩存機制在以后再研究。
13 }

  上面的方法返回的是com.sun.proxy.$Proxy0代理類型,下面就會通過這個代理類型生成代理類。

 3.生成代理類

 1 try {
 2     if (sm != null) {
 3         checkNewProxyPermission(Reflection.getCallerClass(), cl);    //這里還需要做一次檢查,檢查的是生成的代理類型做權限檢查,當然前提還是通過System.setSecurityManager設置安全管理類
 4     }
 5 
 6     final Constructor<?> cons = cl.getConstructor(constructorParams);    //通過反射獲取構造器,cl是代理類型其構造器的參數類型為InvocationHandler,所以參數傳入InvocationHandler
 7     final InvocationHandler ih = h;
 8     if (!Modifier.isPublic(cl.getModifiers())) {   //判斷目標對象的構造器修飾符是我否為public,如果不是則不能生成代理類,返回null
 9         AccessController.doPrivileged(new PrivilegedAction<Void>() {
10             public Void run() {
11                 cons.setAccessible(true);
12                 return null;
13             }
14         });
15     }
16     return cons.newInstance(new Object[]{h});    //最后生成代理類
17 } catch (IllegalAccessException|InstantiationException e) {
18     throw new InternalError(e.toString(), e);
19 } catch (InvocationTargetException e) {
20     Throwable t = e.getCause();
21     if (t instanceof RuntimeException) {
22         throw (RuntimeException) t;
23     } else {
24         throw new InternalError(t.toString(), t);
25     }
26 } catch (NoSuchMethodException e) {
27     throw new InternalError(e.toString(), e);
28 }

  以上就是通過JDK動態代理生成代理類的過程,其中會涉及到動態代理的緩存機制,以及代理類字節碼的生成過程,由於比較復雜,在本文暫不做介紹。由此可以清楚的看到,JDK的動態代理底層是通過Java反射機制實現的,並且需要目標對象繼承自一個接口才能生成它的代理類。

  接下來探討另一種動態代理技術——CGLib。

CGLib動態代理技術

  通過CGLib來創建一個代理需要引入jar包,其pom.xml依賴如下所示:

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.2.4</version>
</dependency>
View Code

  前面提到了CGLib動態代理技術不需要目標對象實現自一個接口:

 1 package cglibproxy;
 2 
 3 /**
 4  * 目標對象(需要被代理的類)
 5  * Created by Kevin on 2017/11/6.
 6  */
 7 public class RealSubject {
 8     public void sayHello() {
 9         System.out.println("hello");
10     }
11 }

  下面我們就使用CGLib代理這個類:

 1 package cglibproxy;
 2 
 3 import net.sf.cglib.proxy.Enhancer;
 4 import net.sf.cglib.proxy.MethodInterceptor;
 5 import net.sf.cglib.proxy.MethodProxy;
 6 
 7 import java.lang.reflect.Method;
 8 
 9 /**
10  * 代理類
11  * Created by Kevin on 2017/11/6.
12  */
13 public class ProxySubject implements MethodInterceptor {
14     private Enhancer enhancer = new Enhancer();
15 
16     public Object getProxy(Class clazz) {
17         enhancer.setSuperclass(clazz);
18         enhancer.setCallback(this);
19         return enhancer.create();    //用於創建無參的目標對象代理類,對於有參構造器則調用Enhancer.create(Class[] argumentTypes, Object[] arguments),第一個參數表示參數類型,第二個參數表示參數的值。
20     }
21 
22     @Override
23     public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
24         System.out.println("調用前");
25         Object result = methodProxy.invokeSuper(object, args);
26         System.out.println("調用后");
27         return result;
28     }
29 }

  可以看到同樣是需要實現一個接口——MethodIntercept,並且實現一個和invoke類似的方法——intercept。

  加入測試代碼:

 1 package cglibproxy;
 2 
 3 /**
 4  * Created by Kevin on 2017/11/6.
 5  */
 6 public class Main {
 7     public static void main(String[] args) {
 8         RealSubject subject = (RealSubject) new ProxySubject().getProxy(RealSubject.class);
 9         subject.sayHello();
10         System.out.println(subject.getClass().getName());
11     }
12 }

  執行結果:

  可以看到的執行結果和JDK動態代理的結果一樣,不同的是代理類的類型是cglibproxy.RealSubject$$EnhancerByCGLIB$$cb568e93。接着我們來看CGLib是如何生成代理類的。

  生成代理類的是ProxySubject類中的getProxy方法,而其中又是傳入兩個參數:

enhancer.setSuperclass(clazz);    //設置需要代理的類
enhancer.setCallback(this);    //設置回調方法

  參數設置好后就調用enhancer.create()方法創建代理類。

1 public Object create() {
2     classOnly = false;    //這個字段設置為false表示返回的是具體的Object代理類,在createClass()方法中設置的是classOnly=true表示的返回class類型的代理類。
3     argumentTypes = null;        //創建的是無參目標對象的代理類,故沒有參數,所以參數類型設置為null
4     return createHelper();
5 }

  看來還在調用一個叫createHelper的方法。

1 private Object createHelper() {
2     preValidate();        //提前作一些校驗
3    //
4     ……
5 }
 1 private void preValidate() {
 2     if (callbackTypes == null) {
 3         callbackTypes = CallbackInfo.determineTypes(callbacks, false);
 4         validateCallbackTypes = true;
 5     }    //檢查回調方法是否為空
 6     if (filter == null) {    //檢查是否設置過濾器,如果設置了多個回調方法就需要設置過濾器
 7         if (callbackTypes.length > 1) {
 8             throw new IllegalStateException("Multiple callback types possible but no filter specified");
 9         }
10         filter = ALL_ZERO;
11     }
12 }

  接着查看createHelper的剩余代碼:

 1 private Object createHelper() {
 2     preValidate();
 3     Object key = KEY_FACTORY.newInstance((superclass != null) ? superclass.getName() : null,
 4             ReflectUtils.getNames(interfaces),
 5             filter == ALL_ZERO ? null : new WeakCacheKey<CallbackFilter>(filter),
 6             callbackTypes,
 7             useFactory,
 8             interceptDuringConstruction,
 9             serialVersionUID);
10     this.currentKey = key;        //在CGLib中也使用到了緩存機制,這段代碼也比較復雜,有關緩存的策略暫時也不做分析吧 
11     Object result = super.create(key);    //利用字節實現並創建代理類對象
12     return result;
13 }

  馬馬虎虎地只能說是介紹了JDK與CGLib兩種動態代理技術,並沒有很深入地研究,特別是在兩者在緩存機制上的實現,略感遺憾。

  另外,在開頭提到了CGLib的性能比JDK高,這實際上並不准確。或許這在特別條件下的確如此,因為在我實測發現JDK8的動態代理效率非常高,甚至略高於CGLib,但是在JDK6的環境下的效率就顯得比較低了。所以,通常所說的CGLib性能比JDK動態代理要高,是傳統的掛念,實際上Java一直都在不斷優化動態代理性能,在比較高版本的JDK條件下可以放行大膽的使用JDK原生的動態代理。

 

 

這是一個能給程序員加buff的公眾號 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM