Spring_AOP_02——實現(xiàn)原理

本文主要講實現(xiàn)AOP的 代理模式原理,以及靜態(tài)代理,動態(tài)代理的區(qū)別和具體實現(xiàn)。

對SpringAOP的概念和使用,可以參考以下文章:
Spring_AOP_01——概念講解
SpringAOP基礎和使用

AOP的實現(xiàn)思路是利用了代理模式,關鍵在于AOP框架對目標對象創(chuàng)建的AOP代理,實現(xiàn)了對目標對象的增強。

AOP的代理方式主要分為兩種,靜態(tài)代理 和 動態(tài)代理。
靜態(tài)代理的代表為AspectJ
Spring使用動態(tài)代理,主要有JDK實現(xiàn)和cglib實現(xiàn)。

Spring創(chuàng)建代理的規(guī)則為:
默認使用JDK動態(tài)代理來創(chuàng)建AOP代理
當需要代理的類沒有實現(xiàn)接口的時候,Spring會切換為使用cglib創(chuàng)建AOP代理
可以在配置文件制定,強制使用cglib代理

本篇主要內(nèi)容:

  • 代理模式
  • AOP實現(xiàn)方式
  • 靜態(tài)代理
  • AspectJ靜態(tài)代理原理
  • 動態(tài)代理
  • JDK和cglib實現(xiàn)動態(tài)代理原理

關于JDK 代理 和 cglib 代理的 源碼層原理,本篇不細講。



代理模式

說到AOP實現(xiàn)原理,那么就首先需要明白什么是代理模式。這里簡單講解一下代理模式的原理。

代理模式使用代理對象完成用戶的請求,控制對元對象的訪問,屏蔽用戶對真實對象的訪問。代理模式是一種弄對象結構型模式。

UML結構圖如下:

代理模式


代理模式的結構
代理模式主要包含三個角色:

  • Subject(抽象主題角色)
    它聲明了真是主題和代理主題的共同接口,這樣一來在任何使用真實主題的地方都可以使用代理主題,客戶端通常需要針對抽象主題角色進行編程。

反映在實際環(huán)境中,這就是我們平時編程時定義的接口和超類。(分別代表JDK代理和cglib代理)

  • Proxy(代理主題角色)
    它包含了對真實主題的引用,從而可以在任何時候操作真實主題對象。在代理主題角色中,提供一個與真實主題角色相同的即可歐,以便在任何時候可以替代真實主題。

代理主題角色還可以控制對真實主題的使用,負責在需要的時候創(chuàng)建和刪除真實主題對象,并對真實主題對象的使用加以約束。

在代理主題角色中,主要任務是定義在代理主題的操作,例如日志打印,權限控制等等,在調(diào)用真實操作之前or之后執(zhí)行的操作。

  • RealSubject(真實主題角色)
    它定義了代理角色所代表的真實對象,在真實主題角色中實現(xiàn)了實際的業(yè)務操作??蛻舳丝梢酝ㄟ^代理角色間接的調(diào)用真實角色中定義的操作。



AOP的實現(xiàn)方式

AOP的實現(xiàn)使用的是代理模式。其中又分為靜態(tài)代理和動態(tài)代理。
靜態(tài)代理的代表為AspectJ
Spring使用動態(tài)代理,主要有JDK實現(xiàn)和cglib實現(xiàn)。

靜態(tài)代理的缺點很明顯
一個代理類只能對一個業(yè)務接口的實現(xiàn)類進行包裝,如果有多個業(yè)務接口的話,就要定義很多實現(xiàn)類進行代理才行。

而且如果代理類對業(yè)務方法的預處理,調(diào)用后處理都是一樣的(例如調(diào)用前輸出提示,調(diào)用后關閉連接),則多個代理類就會有很多重復的代碼。

這時我們可以定義一個這樣的類,它能代理所有實現(xiàn)類的方法調(diào)用:根據(jù)傳進來的業(yè)務實現(xiàn)類和方法名進行具體調(diào)用——那就是動態(tài)代理。

接下來詳細介紹一下各自的實現(xiàn)方式以及區(qū)別:

靜態(tài)代理

靜態(tài)代理就是在編譯期生成代理類的方式。
其實上面的代理模式圖,實現(xiàn)的就是靜態(tài)代理。靜態(tài)代理通常用于對業(yè)務的擴充。通過對真實對象的封裝,來實現(xiàn)擴展性。

代碼簡單實現(xiàn)如下:

// 共同接口
public interface Action {
    public void doSomething();
}

//真實對象
public class RealObject implements Action{
    public void doSomething() {
        System.out.println("do something");
    }
}

//代理對象
public class Proxy implements Action {
    private Action realObject;

    public Proxy(Action realObject) {
        this.realObject = realObject;
    }
    public void doSomething() {
        System.out.println("proxy before do");
        realObject.doSomething();  //調(diào)用真實對象方法
        System.out.println("proxy after do");
    }
}

//運行代碼
Proxy proxy = new Proxy(new RealObject());
proxy.doSomething();

這是一個以接口為抽象主題角色實現(xiàn)的靜態(tài)代理。可以看到,代理對象和真實對象都實現(xiàn)了共同的接口。在代理對象中,保存了一個真實對象的對象引用。并且在調(diào)用對真實對象的操作前后,自己可以做一些其他操作。

AspectJ

AspectJ是一個靜態(tài)代理的增強。需要明確的是AspectJ并不是Spring框架的一部分,而是一套獨立的AOP解決方案。

AspectJ是一個java實現(xiàn)的AOP框架,它能夠?qū)ava代碼進行AOP編譯,讓Java代碼具有AspectJ的AOP功能(一般在編譯器執(zhí)行,需要aspectJ提供的編譯器)

使用AspectJ需要依賴額外的第三方包和aspect的編譯器。


AspectJ織入方式
AspectJ主要采用的是靜態(tài)織入,在這個期間使用AspectJ的acj編譯器(類似javac)把aspect類編譯成class字節(jié)碼后,在java目標類編譯時織入,即先編譯aspect類再編譯目標類。

AspectJ除了編譯期織入,還存在鏈接期(編譯后)織入,即將aspect類和java目標類同時編譯成字節(jié)碼后,再進行織入處理,這種方式比較有助于已編譯好的第三方jar和class文件進行織入操作。(這不是本篇重點,詳細的aspectJ原理可以自行查找資料)

當然,不管是編譯期織入還是編譯后織入,AspectJ都是靜態(tài)代理的方式織入的。即先構建好代理類的class文件,再運行。

關于ajc編譯器,是一種弄能夠識別aspect研發(fā)的編譯器,它采用java語言編寫,由于javac并不鞥識別aspect語法,便有了ajc編譯器。注意ajc編譯器也可以編譯java文件。

AspectJ織入

我們可以反編譯一下ajc織入后的java文件,可以很直觀的看到ajc是如何將代碼織入的。

//編譯后織入aspect類的HelloWord字節(jié)碼反編譯類
public class HelloWord {
    public HelloWord() {
    }

    public void sayHello() {
        System.out.println("hello world !");
    }

    public static void main(String[] args) {
        HelloWord helloWord = new HelloWord();
        HelloWord var10000 = helloWord;

   try {
        //MyAspectJDemo 切面類的前置通知織入
        MyAspectJDemo.aspectOf().ajc$before$com_zejian_demo_MyAspectJDemo$1$22c5541();
        //目標類函數(shù)的調(diào)用
           var10000.sayHello();
        } catch (Throwable var3) {
        MyAspectJDemo.aspectOf().ajc$after$com_zejian_demo_MyAspectJDemo$2$4d789574();
            throw var3;
        }

        //MyAspectJDemo 切面類的后置通知織入 
        MyAspectJDemo.aspectOf().ajc$after$com_zejian_demo_MyAspectJDemo$2$4d789574();
    }
}



動態(tài)代理

與靜態(tài)代理相對的,動態(tài)代理的代理類,是在運行時才被動態(tài)的創(chuàng)建,所以叫做動態(tài)代理。

動態(tài)代理的好處是:對代理類的函數(shù)進行統(tǒng)一管理。解決了解決靜態(tài)代理中存在的功能重復和代碼重復的問題。SpringAOP提供的就是動態(tài)代理支持。

與AspectJ一樣,目的都是為了統(tǒng)一處理橫切業(yè)務,但是與AspectJ不同的是,Spring AOP 并不嘗試提供完成的AOP功能,而是更注重與Spring IOC 容器的結合,并利用該優(yōu)勢來解決橫切業(yè)務的問題。

所以在AOP功能完善方面,AspectJ具有的優(yōu)勢更大。(Spring AOP只能在方法層面做橫切,AspectJ可以對屬性做橫切)

同時,Spring注意到AspectJ在AOP的實現(xiàn)上,依賴于特殊的編譯器(ajc編譯器),因此Spring回避了這一點,Spring采用動態(tài)代理技術的實現(xiàn)原理來構建Spring AOP的內(nèi)部機制(動態(tài)織入)。這是與AspectJ(靜態(tài)織入)最根本的區(qū)別。

在AspectJ 1.5 之后,引入了 @Aspect 形式的注解風格開發(fā),Spring也非??斓馗M了這種方式,在Spring 2.0之后便使用了與Aspect 1.5 一樣的注解。

注意:Spring只是使用了AspectJ的注解,而沒有使用AspectJ的編譯器,低層還是使用動態(tài)代理技術實現(xiàn)。(很重要!)

Spring的 動態(tài)代理主要有兩種方式

  • 使用了JDK Proxy的動態(tài)代理
  • 使用了cglib Ehancer 的動態(tài)代理。


JDK實現(xiàn)動態(tài)代理

JDK動態(tài)代理的原理,是利用反射機制,生成一個和目標類繼承了一樣接口的匿名代理類。在調(diào)用具體方法前使用InvocationHandler來處理。

JDK動態(tài)代理的對象在創(chuàng)建時,需要使用業(yè)務實現(xiàn)類的接口作為參數(shù)(因為在后面代理方法時需要根據(jù)接口內(nèi)的方法名進行調(diào)用)。如果業(yè)務實現(xiàn)類沒有實現(xiàn)接口,就無法使用JDK動態(tài)代理了。并且

關鍵接口:java.lang.reflect.InvocationHandler
關鍵類:java.lang.reflect.Proxyjava.lang.reflect.Method

代碼示例如下:

// JDK代理類
public class JDKProxy implements InvocationHandler {

    private Object proxyObject;

    public JDKProxy(Object proxyObject) {
        this.proxyObject = proxyObject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // do something
        Object ret = null;  //方法返回值

        System.out.println("JDK Proxy --- 調(diào)用前 --- 調(diào)用方法是:"+method.getName() + "---參數(shù)是:"+ GsonUtil.toJson(args));

        ret = method.invoke(proxyObject, args); //調(diào)用invoke方法

        System.out.println("JDK Proxy --- 調(diào)用后");
        return ret;
    }
}

// 獲取代理對象工廠方法
public class ProxyFactory {

    /**
     * 工廠方法,獲取JDKProxy對象
     * @param proxyObject
     * @return
     */
    public static Object createJDKProxyInstance(Object proxyObject){
        JDKProxy jdkProxy = new JDKProxy(proxyObject);
        return Proxy.newProxyInstance(proxyObject.getClass().getClassLoader(), proxyObject.getClass().getInterfaces(), jdkProxy);
    }
}

// 測試代碼
DemoManager jdkDemo = (DemoManager) ProxyFactory.createJDKProxyInstance(new DemoManagerImpl());
jdkDemo.add(1,"Antony");
jdkDemo.delete(2);

// 運行結果
JDK Proxy --- 調(diào)用前 --- 調(diào)用方法是:add---參數(shù)是:[1,"Antony"]
DemoManager add--- 調(diào)用啦---id=1name=Antony
JDK Proxy --- 調(diào)用后


cglib實現(xiàn)動態(tài)代理

cglib動態(tài)代理的原理是繼承需要代理的類,生成的代理類是目標類的子類。用cglib生成的代理類重寫了父類的各個方法,攔截器中的intercept方法內(nèi)容正好就是代理類中的方法體。

cglib是一個代碼生成的類庫,低層是使用了ASM提供的字節(jié)碼操控框架。通過在運行時動態(tài)地生成某個類的子類。cglib是采用繼承的方式實現(xiàn)的代理,所以被聲明為final的類無法代理。

關鍵接口:org.springframework.cglib.proxy.MethodInterceptor
關鍵類:org.springframework.cglib.proxy.Enhancerorg.springframework.cglib.proxy.MethodProxy

代碼示例如下:

// cglib 代理對象的類
public class CGlibProxy implements MethodInterceptor {

    private Object proxyObject;

    public CGlibProxy(Object proxyObject) {
        this.proxyObject = proxyObject;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("CGLib Proxy --- 調(diào)用前 --- 調(diào)用方法是:"+method.getName() + "---參數(shù)是:"+ GsonUtil.toJson(objects));

        Object ret = method.invoke(proxyObject, objects);

        System.out.println("CGLib Proxy --- 調(diào)用后");

        return ret;
    }
}

// 工廠方法,獲取代理對象
public class ProxyFactory {
    /**
     * 工廠方法,獲取cglibProxy對象
     * @param proxyObject
     * @return
     */
    public static Object createCGlibProxyInstance(Object proxyObject){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(proxyObject.getClass());
        enhancer.setCallback(new CGlibProxy(proxyObject));
        return enhancer.create();
    }
}

// 測試代碼
DemoManager cgDemo = (DemoManager) ProxyFactory.createCGlibProxyInstance(new DemoManagerImpl());
cgDemo.add(1,"Antony");
cgDemo.delete(2);

// 運行結果
CGLib Proxy --- 調(diào)用前 --- 調(diào)用方法是:add---參數(shù)是:[1,"Antony"]
DemoManager add--- 調(diào)用啦---id=1name=Antony
CGLib Proxy --- 調(diào)用后


兩者的區(qū)別
JDK 使用繼承接口的方式生成代理類。(實現(xiàn)接口,管理代理實例)
cglib 使用繼承目標類的方式生成代理類。(生成目標類的子類,重寫方法)

JDK只能對繼承了接口的類代理。
cglib 除了 final 類都可以代理。

JDK代理類生成快,但是運行效率較cglib代理差。
cglib代理類生成慢,但是運行效率較JDK代理快。

Spring如何選擇這兩種代理

  • 目標對象實現(xiàn)了接口,默認使用JDK代理。
  • 目標對象實現(xiàn)了接口,可以選擇強制使用 cglib 代理。
  • 目標對象沒有實現(xiàn)接口,只能選擇使用 cglib 代理。


為什么不全部使用cglib

cglib創(chuàng)建代理類的速度較慢,但是創(chuàng)建后運行的速度則很快。而JDK代理正好相反。如果在運行時全部使用cglib代理,則系統(tǒng)性能會顯著下降。所以一般建議在系統(tǒng)初始化的時候使用cglib方式創(chuàng)建代理,放入Spring的ApplicationContext中以備后用。

SpringAOP 和 AspectJ的區(qū)別

Spring AOP AspectJ
在純 Java 中實現(xiàn) 使用 Java 編程語言的擴展實現(xiàn)
不需要單獨的編譯過程 除非設置 LTW,否則需要 AspectJ 編譯器 (ajc)
只能使用運行時織入 運行時織入不可用。支持編譯時、編譯后和加載時織入
功能不強-僅支持方法級編織 更強大 - 可以編織字段、方法、構造函數(shù)、靜態(tài)初始值設定項、最終類/方法等......。
只能在由 Spring 容器管理的 bean 上實現(xiàn) 可以在所有域?qū)ο笊蠈崿F(xiàn)
僅支持方法執(zhí)行切入點 支持所有切入點
代理是由目標對象創(chuàng)建的, 并且切面應用在這些代理上 在執(zhí)行應用程序之前 (在運行時) 前, 各方面直接在代碼中進行織入
比 AspectJ 慢多了 更好的性能
易于學習和應用 相對于 Spring AOP 來說更復雜



(如果有什么錯誤或者建議,歡迎留言指出)
(本文內(nèi)容是對各個知識點的轉(zhuǎn)載整理,用于個人技術沉淀,以及大家學習交流用)


參考資料:
關于SpringAOP你該知曉的一切
AOP低層實現(xiàn)——JDK和CGLIB的動態(tài)代理

基于SpringAOP的 JDK動態(tài)代理和CGLIB動態(tài)代理
SpringAOP的兩種代理方式:JDK動態(tài)代理和CGLIB動態(tài)代理

設計模式——代理模式
靜態(tài)代理和動態(tài)代理的理解(附有源碼分析)
AspectJ與SpringAOP的比較

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容