Java動態代理的原生實現和Spring AOP的實現

Java中的動態代理

動態代理

是動態地創建代理並動態地處理對所代理方法的調用。

實現動態代理需要實現InvocationHandler接口,實現其invoke(object, method, args[])函數,傳遞的是一個代理實例(Proxy類庫的$Proxy0)、方法和參數。

Java動態代理的創建

動態代理對象是用靜態方法Proxy.newProxyInstance()方法創建的:

  1. 第一個參數是希望代理的接口的類加載器,
  2. 第二個參數是希望該代理實現的接口列表(Class對象數組),
  3. 第三個參數是InvocationHandler接口的一個實現(Proxy.newProxyInstance()返回值可以強制轉換成接口,不能強制轉換成實現類,因此Java動態代理,只能代理接口,不能代理類【只能代理接口的意思是,創建代理後,只能通過接口的方式來調用代理的實現。】)。


動態代理代理的方法都會經過第三個參數的對象所實現的invoke方法來進行代理調用,因此通常會向調用處理器傳遞一個“實際對象”的引用(如下面的readObject對象),從而使得調用處理器可以將請求轉發。如下圖:

Java動態代理的原生實現和Spring AOP的實現

上圖中:

  1. 代理類實例proxy的類名是:$Proxy0
  2. proxy中的屬性有:m1, m0, m3, m4, m2
  3. proxy中的方法有:equals, toString, hashCode, doSomething, somethingElse
  4. proxy的父類是:class java.lang.reflect.Proxy
  5. proxy實現的接口是:opensource.Test$Interface

動態代理的作用

動態代理主要用來做方法的增強,讓你可以在不修改源碼的情況下,增強一些方法,在方法執行前後做任何你想做的事情(甚至根本不去執行這個方法),因為在InvocationHandler的invoke方法中,你可以直接獲取正在調用方法對應的Method對象,具體應用的話,比如可以添加調用日誌,做事務控制等。還有一個有趣的作用是可以用作遠程調用,比如現在有Java接口,這個接口的實現部署在其它服務器上,在編寫客戶端代碼的時候,沒辦法直接調用接口方法,因為接口是不能直接生成對象的,這個時候就可以考慮代理模式(動態代理)了,通過Proxy.newProxyInstance代理一個該接口對應的InvocationHandler對象,然後在InvocationHandler的invoke方法內封裝通訊細節就可以了。具體的應用,最經典的當然是Java標準庫的RMI,其它比如hessian,各種webservice框架中的遠程調用,大致都是這麼實現的。見下面的例子:

<code>package com.proxy.test;public interface UserLogin {    public void login(String userName);}/<code>


<code>package com.proxy.test;public class UserLoginImpl implements UserLogin {    public void login(String userName) {        System.out.println("歡迎 " + userName + " 登錄系統");    }}/<code>


<code>package com.proxy.test;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;public class MyHandler implements InvocationHandler {    private Object obj;    public MyHandler(Object obj) {        this.obj = obj;    }    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        beforeLogin(); // 登錄前處理,更具自己需要來寫        Object result = method.invoke(obj, args); // 調用真正的方法        afterLogin(); // 登錄後處理,更具自己需要來寫        return result;    }    public void beforeLogin() {        System.out.println("登錄前處理");    }    public void afterLogin() {        System.out.println("登錄後處理");    }}/<code>


<code>package com.proxy.test;import java.lang.reflect.Proxy;public class ProxyTest {    public static void main(String[] args) {        UserLoginImpl user = new UserLoginImpl(); // 得到實例對象        MyHandler handler = new MyHandler(user); // 將對象傳入自己的處理器中        UserLogin proxy = (UserLogin) Proxy.newProxyInstance(user.getClass().getClassLoader(), user.getClass()                .getInterfaces(), handler); // 得到代理對象        proxy.login("張三"); // 代理調用login方法    }}/<code>


Spring AOP動態代理

Spring AOP通過代理模式實現,目前支持兩種代理:

  1. JDK動態代理。
  2. CGLIB代理來創建AOP代理。

Spring建議優先使用JDK動態代理。

JDK動態代理

使用java.lang.reflect.Proxy動態代理實現,即提取目標對象的接口,然後對接口創建AOP代理。

CGLIB代理

CGLIB代理不僅能進行接口代理,也能進行類代理。

CGLIB代理需要注意以下問題:不能代理final方法,因為final方法不能被覆蓋(CGLIB通過生成子類來創建代理);會產生兩次構造器調用,第一次是目標類的構造器調用,第二次是CGLIB生成的代理類的構造器調用(CGLIB要求被代理的類要有默認的構造函數,因為子類實例化時會隱式調用super());如果需要CGLIB代理方法,請確保兩次構造器調用不影響應用。


Spring AOP默認首先使用JDK動態代理來代理目標對象,如果目標對象沒有實現任何接口將使用CGLIB代理,如果需要強制使用CGLIB代理,請使用如下方式指定:

  1. 對於Schema風格配置切面使用如下方式來指定使用CGLIB代理:<config>
  2. 而如果使用@AspectJ風格使用如下方式來指定使用CGLIB代理:<aspectj-autoproxy>



好書推薦!Java界公認的權威書籍!堅持讀完,輕鬆拿下阿里巴巴offer!下方鏈接直接京東自營購買!



分享到:


相關文章: