SpringAOP概念

1. AOP概念

1.1 定義

AOP全稱為Aspet-Oriented Programming,中文名字為面向切面編程赦拘。使用AOP另绩,我們可以對類似日志和安全等系統(tǒng)需求進行模塊化的組織笋籽,簡化系統(tǒng)需求與實現(xiàn)之間的對比關系,進而使得整個系統(tǒng)的實現(xiàn)更具模塊化笛园。

1.2 AOP國家的公民

1.2.1 Joinpoint

在系統(tǒng)運行之前研铆,AOP的功能模塊都需要織入到OOP的功能模塊中棵红。所以逆甜,要進行這種織入過程交煞,我們需要知道在系統(tǒng)的哪些執(zhí)行點上進行織入操作素征,這些將要在其之上進行織入操作的系統(tǒng)執(zhí)行點就稱之為Joinpoint御毅。

以下是一些較為常見的Joinpoint類型

  • 方法調(diào)用
  • 方法執(zhí)行
  • 構造方法調(diào)用
  • 構造方法執(zhí)行
  • 字段設置
  • 字段獲取
  • 異常處理執(zhí)行
  • 類初始化

1.2.2 Pointcut

Pointcut概念代表的是Joinpoint的表述方式亚享。將橫切邏輯織入當前系統(tǒng)的過程中绘面,需要參照Pointcut規(guī)定的Joinpoint信息揭璃,才可以知道應該往系統(tǒng)的哪些Joinpoint上織入橫切邏輯。

1.2.3 Advice

是單一橫切關注點邏輯的載體歼秽,它代表將會織入到Joinpoint的橫切邏輯箩祥。

Advice可以分成多種具體形式肆氓。

  • Before Advice
  • After Advice
  • Around Advice

1.2.4 Aspect

Aspect是對系統(tǒng)中的橫切關注點邏輯進行模塊化封裝的AOP概念實體蕉陋。通常情況下拨扶,Aspect可以包含多個Pointcut以及相關Advice定義缩举。

1.2.5 Weaving

用來將Aspect模塊化的橫切關注點集成到OOP的現(xiàn)存系統(tǒng)中蚁孔。

1.2.6 目標對象

符合Pointcut所指定的條件,將在織入過程中被織入橫切邏輯的對象另伍。

最后附上一張AOP各個概念所處的場景圖摆尝。
[圖片上傳失敗...(image-26c2fc-1616160107453)]

1.3 Java平臺上的AOP實現(xiàn)機制

1.3.1 動態(tài)代理

JDK1.3之后堕汞,引入了動態(tài)代理(Dynamic Proxy)機制讯检,可以在運行期間人灼,為相應的接口(Interface)動態(tài)生成對應的代理對象投放。所以适贸,我們可以將橫切關注點邏輯封裝到動態(tài)代理的InvocationHandler中,然后在系統(tǒng)運行期間烙样,根據(jù)橫切關注點需要織入的模塊位置误阻,將橫切邏輯織入到相應的代理類中寻定。以動態(tài)代理類為載體的橫切邏輯狼速,現(xiàn)在當然就可以與系統(tǒng)其他實現(xiàn)模塊一起工作了。

這種方式實現(xiàn)的唯一缺點是惊完,所有需要織入橫切關注點邏輯的模塊類都得實現(xiàn)相應的接口僵芹。

SpringAOP默認情況采用這種機制來實現(xiàn)AOP機能。

1.3.2 動態(tài)字節(jié)碼增強

通常的class文件都是從Java源代碼文件使用Javac編譯器編譯而成的小槐,但只要符合Javaclass規(guī)范拇派,我們也可以使用ASM或者CGLIB等Java工具庫,在程序運行期間凿跳,動態(tài)構建字節(jié)碼的class文件件豌。

Spring AOP在無法采用動態(tài)代理機制進行AOP功能擴展的時候,會使用CGLIB庫的動態(tài)字節(jié)碼增強支持來實現(xiàn)AOP的功能擴展控嗜。

1.3.3 Java代碼生成

事務屬于跨越整個系統(tǒng)的一種橫切關注點茧彤,所以,EJB容器提供的聲明性事務支持疆栏,屬于一種AOP功能模塊實現(xiàn)。但早期EJB容器在實現(xiàn)這一功能的時候险污,大多會采用Java代碼生成技術,這就是我們不需要提供CMP的接口實現(xiàn)類的原因淮逻,也是EJB容器提供商大多提供部署接口或者專有部署工具的原因。

1.3.4 自定義類加載器

我們可以通過自定義類加載器的方式完成橫切邏輯到系統(tǒng)的織入,自定義類加載器通過讀取外部文件規(guī)定的織入規(guī)則和必要信息桨啃,在加載class文件期間就可以將橫切邏輯添加到系統(tǒng)模塊類的現(xiàn)有邏輯中照瘾,然后將改動后的class交給Java虛擬機運行碳却。

1.3.5 AOL擴展

AOL擴展是最強大关噪、也最難掌握的一種方式虐沥,我們之前提到的AspectJ就屬于這種方式。在這種方式中喜每,AOP的各種概念在AOL中大都有一一對應的實體。我們可以使用擴展過的AOL,實現(xiàn)任何AOP概念實體甚至OOP概念實體,比如Aspect以及Class。所有的AOP概念在AOL中得到了最完美的表達饵较。

2. SpringAOP的實現(xiàn)機制

SpingAOP屬于第二代AOP茄猫,采用動態(tài)代理機制和字節(jié)碼生成技術實現(xiàn)靖避。

先從動態(tài)代理機制的根源--代理模式開始介紹蚣抗。

2.1 代理模式

在代理模式中通常涉及四種角色,如圖所示。


代理模式類圖
  • ISubject:該接口是對被訪問者或者被訪問資源的抽象
  • SubjectImpl:被訪問者或者被訪問資源的具體實現(xiàn)類
  • SubjectProxy:被訪問者或者被訪問資源的代理實現(xiàn)類抵屿,該類持有一個ISubject接口的具體實例求晶。在這個場景中慢蜓,我們要SubjectImpl進行代理镜遣,那么SubjectProxy現(xiàn)在持有的就是SubjectImpl的實例
  • Client赤拒。代表訪問者的抽象角色挎挖。

SubjectImpl和SubjectProxy都實現(xiàn)了相同的接口ISubject,而SubjectProxy內(nèi)部持有SubjectImpl的引用。當Client通過request()請求服務的時候,SubjectProxy將轉發(fā)該請求給SubjectImpl孕锄。從這個角度來說轴脐,SubjectProxy反而有多此一舉之嫌了现使。不過间景,SubjectProxy的作用不只局限于請求的轉發(fā),更多時候是對請求添加更多訪問限制艺智。

在將請求轉發(fā)給被代理對象SubjectImp1之前或者之后倘要,都可以根據(jù)情況插入其他處理邏輯,比如在轉發(fā)之前記錄方法執(zhí)行開始時間十拣,在轉發(fā)之后記錄結束時間封拧,這樣就能夠?qū)ubjectImpl的 request()執(zhí)行的時間進行檢測∝参剩或者泽西,可以只在轉發(fā)之后對SubjectImpl的request()方法返回結果進行覆蓋,返回不同的值缰趋。甚至捧杉,可以不做請求轉發(fā),這樣秘血,就不會有SubjectImpl的訪問發(fā)生味抖。如果你不希望某人訪問你的SubjectImpl,這種場景正好適合灰粮。

代理對象subjectProxy就像是subjectImpl的影子非竿,只不過這個影子通常擁有更多的功能。如果 SubjectImpl是系統(tǒng)中的Joinpoint所在的對象谋竖,即目標對象红柱,那么就可以為這個目標對象創(chuàng)建一個代理對象,然后將橫切邏輯添加到這個代理對象中蓖乘。

SpringAOP本質(zhì)上就是采用這種代理機制實現(xiàn)的锤悄,但是,具體實現(xiàn)細節(jié)上有所不同嘉抒。

2.1.1 示例代碼

先上代碼零聚,把上邊的流程圖變?yōu)榫唧w代碼。

package com.spring.aop;

public interface ISubject {

    void request();

}
package com.spring.aop;

public class SubjectImpl implements ISubject {

    @Override
    public void request() {
        System.out.println("執(zhí)行代理類的方法");
    }
}
package com.spring.aop;

public class SubjectProxy implements ISubject {

    private ISubject iSubject;

    public SubjectProxy(ISubject iSubject) {
        this.iSubject = iSubject;
    }

    @Override
    public void request() {
        //添加前置邏輯
        iSubject.request();
        //添加后置邏輯
    }
}
package com.spring.aop;

public class Client {

    public static void main(String[] args) {
        SubjectProxy subjectProxy = new SubjectProxy(new SubjectImpl());
        subjectProxy.request();
    }

}

假設我們要對系統(tǒng)中所有的request()方法進行攔截些侍,那么我們應該為SubjectImpl提供一個ServiceControlSubjectProxy隶症,以添加這種橫切邏輯。

package com.spring.aop;

public class ServiceControlSubjectProxy implements ISubject {

    private ISubject subject;

    public ServiceControlSubjectProxy(ISubject subject) {
        this.subject = subject;
    }

    @Override
    public void request() {
        //添加新的前置邏輯
        subject.request();
        //添加新的后置邏輯
    }
}

之后我們使用ServiceControlSubjectProxy代替SubjectImpl使用岗宣,如下所示

        ServiceControlSubjectProxy serviceControlSubjectProxy = 
                new ServiceControlSubjectProxy(new SubjectImpl());
        serviceControlSubjectProxy.request();

但是系統(tǒng)中不一定就ISubject的實現(xiàn)類有request()方法蚂会,如果要對所有request()方法都添加這個邏輯,那么還需要新寫一個代理類耗式,而實際上胁住,這些代理對象所要添加的橫切邏輯是一樣的。當系統(tǒng)中存在成百上千目標對象時刊咳,我們就要為這成百上千的目標對象創(chuàng)建成百上千的代理對象彪见。

2.2 動態(tài)代理

JDK1.3之后引入了一種稱之為動態(tài)代理(Dynamic Proxy)的機制。使用該機制娱挨,我們可以為指定的接口在系統(tǒng)運行期間動態(tài)地生成代理對象余指,從而幫助我們走出最初使用靜態(tài)代理實現(xiàn)AOP的窘境。

動態(tài)代理機制的實現(xiàn)主要由一個類和一個接口組成跷坝,即java.lang.reflect.Proxy類和java.lang.reflect.InvocationH?ndler接口酵镜。下面,讓我們看一下探孝,如何使用動態(tài)代理來實現(xiàn)之前的功能笋婿。雖然要為ISubject和TRequestable兩種類型提供代理對象,但因為代理對象中要添加的橫切邏輯是一樣的顿颅,所以缸濒,我們只需要實現(xiàn)一個TnvocationHandler就可以了。

2.2.1 具體代碼

package com.spring.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class ProxyInvocationHandler implements InvocationHandler {

    private Object target;

    public ProxyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("添加前置邏輯");
        Object object = method.invoke(target, args);
        System.out.println("添加后置邏輯");
        return object;
    }
}

package com.spring.aop;

import java.lang.reflect.Proxy;

public class Client {

    public static void main(String[] args) {
        ISubject subject = new SubjectImpl();
        subject = (ISubject) Proxy.newProxyInstance(
                subject.getClass().getClassLoader(),
                subject.getClass().getInterfaces(),
                new ProxyInvocationHandler(subject)
        );

        subject.request();

        IRequestable requestable = new RequestableImpl();
        requestable = (IRequestable) Proxy.newProxyInstance(
                requestable.getClass().getClassLoader(),
                requestable.getClass().getInterfaces(),
                new ProxyInvocationHandler(requestable)
        );

        requestable.request();

    }
}

動態(tài)代理雖好粱腻,但不能滿足所有的需求庇配。因為動態(tài)代理機制只能對實現(xiàn)了相應Interface的類使用,如果某個類沒有實現(xiàn)任何的Interface绍些,就無法使用動態(tài)代理機制為其生成相應的動態(tài)代理對象捞慌。雖然面向接口編程應該是提倡的做法,但不排除其他的編程實踐柬批。對于沒有實現(xiàn)任何Interface的目標對象啸澡,我們需要尋找其他方式為其動態(tài)的生成代理對象袖订。

默認情況下,如果Spring AOP發(fā)現(xiàn)目標對象實現(xiàn)了相應Interface嗅虏,則采用動態(tài)代理機制為其生成代理對象實例洛姑。而如果目標對象沒有實現(xiàn)任何Interface,Spring AOP會嘗試使用一個稱為CGLIB(Code GenerationLibrary)的開源的動態(tài)字節(jié)碼生成類庫皮服,為目標對象生成動態(tài)的代理對象實例楞艾。

2.3 動態(tài)字節(jié)碼生成

使用動態(tài)字節(jié)碼生成技術擴展對象行為的原理是,我們可以對目標對象進行繼承擴展龄广,為其生成相應的子類硫眯,而子類可以通過覆寫來擴展父類的行為,只要將橫切邏輯的實現(xiàn)放到子類中择同,然后讓系統(tǒng)使用擴展后的目標對象的子類两入,就可以達到與代理模式相同的效果了。

但是奠衔,使用繼承的方式來擴展對象定義谆刨,也不能像靜態(tài)代理模式那樣,為每個不同類型的目標對象都單獨創(chuàng)建相應的擴展子類归斤。所以痊夭,我們要借助于CGLIB這樣的動態(tài)字節(jié)碼生成庫,在系統(tǒng)運行期間動態(tài)地為目標對象生成相應的擴展子類脏里。

2.3.1 代碼

package com.spring.aop;

public class Subject {

    public void request(){
        System.out.println("沒有接口的目標類");
    }

}
package com.spring.aop;


import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CGLIBProxy implements MethodInterceptor {

    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("這是CGLIB前置邏輯");
        Object result = methodProxy.invokeSuper(object, args);
        System.out.println("這是CGLIB后置邏輯");
        return result;
    }
}
package com.spring.aop;

import org.springframework.cglib.proxy.Enhancer;

public class Client {

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Subject.class);
        enhancer.setCallback(new CGLIBProxy());
        Subject proxy = (Subject) enhancer.create();
        proxy.request();
    }
}

3. SpringAOP中的術語

  • 切面(Aspect):指關注點模塊化她我,這個關注點可能會橫切多個對象。事務管理是企業(yè)級Java應用中有關橫切關注點的例子迫横。在Spring AOP中番舆,切面可以使用通用類基于模式的方法或者在普通類中以@Aspect注解來實現(xiàn)
  • 連接點(Join point):在Spring AOP中,一個連接點總是代表一個方法的執(zhí)行矾踱,其實就代表增強的方法
  • 通知(Advice):在切面的某個特定的連接點上執(zhí)行的動作恨狈。通知有多種類型,包括"around"呛讲、"before"和"after"等待禾怠。通知的類型將在后面的章節(jié)進行討論。許多AOP框架贝搁,包括Spring在內(nèi)吗氏,都是以攔截器做通知模型的,并維護著一個以連接點為中心的攔截器鏈
  • 目標對象(Target):目標對象指將要被增強的對象雷逆。即包含主業(yè)務邏輯的類的對象
  • 切點(Pointcut):匹配連接點的斷言弦讽。通知和切點表達式相關聯(lián),并在滿足這個切點的連接點上運行(例如膀哲,當執(zhí)行某個特定名稱的方法時)往产。切點表達式如何和連接點匹配是AOP的核心:Spring默認使用AspectJ切點語義
  • 顧問(Advisor):顧問是Advice的一種包裝體現(xiàn)被碗,Advisor是Pointcut以及Advice的一個結合,用來管理Advice和Pointcut捂齐。應用無需關心
  • 織入(Weaving):將通知切入連接點的過程叫織入
  • 引入(Introductions):可以將其他接口和實現(xiàn)動態(tài)引入到targetClass中
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蛮放,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子奠宜,更是在濱河造成了極大的恐慌,老刑警劉巖瞻想,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件压真,死亡現(xiàn)場離奇詭異,居然都是意外死亡蘑险,警方通過查閱死者的電腦和手機滴肿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來佃迄,“玉大人泼差,你說我怎么就攤上這事『乔危” “怎么了堆缘?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長普碎。 經(jīng)常有香客問我吼肥,道長,這世上最難降的妖魔是什么麻车? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任缀皱,我火速辦了婚禮,結果婚禮上动猬,老公的妹妹穿的比我還像新娘啤斗。我一直安慰自己,他們只是感情好赁咙,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布钮莲。 她就那樣靜靜地躺著,像睡著了一般序目。 火紅的嫁衣襯著肌膚如雪臂痕。 梳的紋絲不亂的頭發(fā)上罩息,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天败砂,我揣著相機與錄音腕巡,去河邊找鬼菌羽。 笑死声邦,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的豹爹。 我是一名探鬼主播滑蚯,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼肥卡!你這毒婦竟也來了溪掀?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤步鉴,失蹤者是張志新(化名)和其女友劉穎揪胃,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體氛琢,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡喊递,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了阳似。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片骚勘。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖撮奏,靈堂內(nèi)的尸體忽然破棺而出俏讹,到底是詐尸還是另有隱情,我是刑警寧澤畜吊,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布泽疆,位于F島的核電站,受9級特大地震影響定拟,放射性物質(zhì)發(fā)生泄漏于微。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一青自、第九天 我趴在偏房一處隱蔽的房頂上張望株依。 院中可真熱鬧,春花似錦延窜、人聲如沸恋腕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荠藤。三九已至,卻和暖如春获高,著一層夾襖步出監(jiān)牢的瞬間哈肖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工念秧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留淤井,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像币狠,于是被迫代替她去往敵國和親游两。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容