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

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

對(duì)SpringAOP的概念和使用,可以參考以下文章:
Spring_AOP_01——概念講解
SpringAOP基礎(chǔ)和使用

AOP的實(shí)現(xiàn)思路是利用了代理模式,關(guān)鍵在于AOP框架對(duì)目標(biāo)對(duì)象創(chuàng)建的AOP代理,實(shí)現(xiàn)了對(duì)目標(biāo)對(duì)象的增強(qiáng)。

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

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

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

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

關(guān)于JDK 代理 和 cglib 代理的 源碼層原理,本篇不細(xì)講。



代理模式

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

代理模式使用代理對(duì)象完成用戶的請(qǐng)求,控制對(duì)元對(duì)象的訪問,屏蔽用戶對(duì)真實(shí)對(duì)象的訪問。代理模式是一種弄對(duì)象結(jié)構(gòu)型模式。

UML結(jié)構(gòu)圖如下:

代理模式


代理模式的結(jié)構(gòu)
代理模式主要包含三個(gè)角色:

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

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

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

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

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

  • RealSubject(真實(shí)主題角色)
    它定義了代理角色所代表的真實(shí)對(duì)象,在真實(shí)主題角色中實(shí)現(xiàn)了實(shí)際的業(yè)務(wù)操作。客戶端可以通過代理角色間接的調(diào)用真實(shí)角色中定義的操作。



AOP的實(shí)現(xiàn)方式

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

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

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

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

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

靜態(tài)代理

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

代碼簡(jiǎn)單實(shí)現(xiàn)如下:

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

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

//代理對(duì)象
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)用真實(shí)對(duì)象方法
        System.out.println("proxy after do");
    }
}

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

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

AspectJ

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

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

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


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

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

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

關(guān)于ajc編譯器,是一種弄能夠識(shí)別aspect研發(fā)的編譯器,它采用java語言編寫,由于javac并不鞥識(shí)別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();
        //目標(biāo)類函數(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();
    }
}



動(dòng)態(tài)代理

與靜態(tài)代理相對(duì)的,動(dòng)態(tài)代理的代理類,是在運(yùn)行時(shí)才被動(dòng)態(tài)的創(chuàng)建,所以叫做動(dòng)態(tài)代理

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

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

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

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

在AspectJ 1.5 之后,引入了 @Aspect 形式的注解風(fēng)格開發(fā),Spring也非常快地跟進(jìn)了這種方式,在Spring 2.0之后便使用了與Aspect 1.5 一樣的注解。

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

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

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


JDK實(shí)現(xiàn)動(dòng)態(tài)代理

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

JDK動(dòng)態(tài)代理的對(duì)象在創(chuàng)建時(shí),需要使用業(yè)務(wù)實(shí)現(xiàn)類的接口作為參數(shù)(因?yàn)樵诤竺娲矸椒〞r(shí)需要根據(jù)接口內(nèi)的方法名進(jìn)行調(diào)用)。如果業(yè)務(wù)實(shí)現(xiàn)類沒有實(shí)現(xiàn)接口,就無法使用JDK動(dòng)態(tài)代理了。并且

關(guān)鍵接口:java.lang.reflect.InvocationHandler
關(guān)鍵類: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;
    }
}

// 獲取代理對(duì)象工廠方法
public class ProxyFactory {

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

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

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


cglib實(shí)現(xiàn)動(dòng)態(tài)代理

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

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

關(guān)鍵接口:org.springframework.cglib.proxy.MethodInterceptor
關(guān)鍵類:org.springframework.cglib.proxy.Enhancerorg.springframework.cglib.proxy.MethodProxy

代碼示例如下:

// cglib 代理對(duì)象的類
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;
    }
}

// 工廠方法,獲取代理對(duì)象
public class ProxyFactory {
    /**
     * 工廠方法,獲取cglibProxy對(duì)象
     * @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();
    }
}

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

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


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

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

JDK代理類生成快,但是運(yùn)行效率較cglib代理差。
cglib代理類生成慢,但是運(yùn)行效率較JDK代理快。

Spring如何選擇這兩種代理

  • 目標(biāo)對(duì)象實(shí)現(xiàn)了接口,默認(rèn)使用JDK代理。
  • 目標(biāo)對(duì)象實(shí)現(xiàn)了接口,可以選擇強(qiáng)制使用 cglib 代理。
  • 目標(biāo)對(duì)象沒有實(shí)現(xiàn)接口,只能選擇使用 cglib 代理。


為什么不全部使用cglib

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

SpringAOP 和 AspectJ的區(qū)別

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



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


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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。