一、 AOP 簡(jiǎn)介
1.1 什么是 AOP
AOP (Aspect Orient Programming),直譯過(guò)來(lái)就是 面向切面編程。AOP 是一種編程思想,是面向?qū)ο缶幊蹋∣OP)的一種補(bǔ)充。面向?qū)ο缶幊虒⒊绦虺橄蟪筛鱾€(gè)層次的對(duì)象,而面向切面編程是將程序抽象成各個(gè)切面。
1.2 為什么需要 AOP
OOP引入封裝、繼承和多態(tài)性等概念來(lái)建立一種對(duì)象層次結(jié)構(gòu),用以模擬公共行為的一個(gè)集合。當(dāng)我們需要為分散的對(duì)象引入公共行為的時(shí)候,OOP則顯得無(wú)能為力。通過(guò)案例,感受一下:
A類(lèi):
public class A {
public void executeA(){
//其他業(yè)務(wù)操作省略......
recordLog();
}
public void recordLog(){
//....記錄日志并上報(bào)日志系統(tǒng)
}
}
B類(lèi):
public class B {
public void executeB(){
//其他業(yè)務(wù)操作省略......
recordLog();
}
public void recordLog(){
//....記錄日志并上報(bào)日志系統(tǒng)
}
}
C類(lèi):
public class C {
public void executeC(){
//其他業(yè)務(wù)操作省略......
recordLog();
}
public void recordLog(){
//....記錄日志并上報(bào)日志系統(tǒng)
}
}
假設(shè)存在A、B、C三個(gè)類(lèi),需要對(duì)它們的方法訪問(wèn)進(jìn)行日志記錄,在代碼中各種存在recordLog方法進(jìn)行日志記錄并上報(bào),或許對(duì)現(xiàn)在的工程師來(lái)說(shuō)幾乎不可能寫(xiě)出如此糟糕的代碼,但在OOP這樣的寫(xiě)法是允許的,而且在OOP開(kāi)始階段這樣的代碼確實(shí)并大量存在著,直到工程師實(shí)在忍受不了一次修改,到處挖墳時(shí)(修改recordLog內(nèi)容),才下定決心解決該問(wèn)題,為了解決程序間過(guò)多冗余代碼的問(wèn)題,工程師便開(kāi)始使用下面的編碼方式:
//A類(lèi)
public class A {
public void executeA(){
//其他業(yè)務(wù)操作省略...... args 參數(shù),一般會(huì)傳遞類(lèi)名,方法名稱 或信息(這樣的信息一般不輕易改動(dòng))
Report.recordLog(args ...);
}
}
//B類(lèi)
public class B {
public void executeB(){
//其他業(yè)務(wù)操作省略......
Report.recordLog(args ...);
}
}
//C類(lèi)
public class C {
public void executeC(){
//其他業(yè)務(wù)操作省略......
Report.recordLog(args ...);
}
}
//record
public class Report {
public static void recordLog(args ...){
//....記錄日志并上報(bào)日志系統(tǒng)
}
}
這樣操作后,我們欣喜地發(fā)現(xiàn)問(wèn)題似乎得到了解決,當(dāng)上報(bào)信息內(nèi)部方法需要調(diào)整時(shí),只需調(diào)整Report類(lèi)中recordLog方法體,也就避免了隨處挖墳的問(wèn)題,大大降低了軟件后期維護(hù)的復(fù)雜度。確實(shí)如此,而且除了上述的解決方案,還存在一種通過(guò)繼承來(lái)解決的方式,采用這種方式,只需把相通的代碼放到一個(gè)類(lèi)(一般是其他類(lèi)的父類(lèi))中,其他的類(lèi)(子類(lèi))通過(guò)繼承父類(lèi)獲取相通的代碼,如下:
//通用父類(lèi)
public class Dparent {
public void commond(){
//通用代碼
}
}
//A 繼承 Dparent
public class A extends Dparent {
public void executeA(){
//其他業(yè)務(wù)操作省略......
commond();
}
}
//B 繼承 Dparent
public class B extends Dparent{
public void executeB(){
//其他業(yè)務(wù)操作省略......
commond();
}
}
//C 繼承 Dparent
public class C extends Dparent{
public void executeC(){
//其他業(yè)務(wù)操作省略......
commond();
}
}
顯然代碼冗余也得到了解決,這種通過(guò)繼承抽取通用代碼的方式也稱為縱向拓展,與之對(duì)應(yīng)的還有橫向拓展。事實(shí)上有了上述兩種解決方案后,在大部分業(yè)務(wù)場(chǎng)景的代碼冗余問(wèn)題也得到了實(shí)實(shí)在在的解決。
但是隨著軟件開(kāi)發(fā)的系統(tǒng)越來(lái)越復(fù)雜,工程師認(rèn)識(shí)到,傳統(tǒng)的OOP程序經(jīng)常表現(xiàn)出一些不自然的現(xiàn)象,核心業(yè)務(wù)中總摻雜著一些不相關(guān)聯(lián)的特殊業(yè)務(wù),如日志記錄,權(quán)限驗(yàn)證,事務(wù)控制,性能檢測(cè),錯(cuò)誤信息檢測(cè)等等,這些特殊業(yè)務(wù)可以說(shuō)和核心業(yè)務(wù)沒(méi)有根本上的關(guān)聯(lián)而且核心業(yè)務(wù)也不關(guān)心它們,比如在用戶管理模塊中,該模塊本身只關(guān)心與用戶相關(guān)的業(yè)務(wù)信息處理,至于其他的業(yè)務(wù)完全可以不理會(huì),我們看一個(gè)簡(jiǎn)單例子協(xié)助理解這個(gè)問(wèn)題
public interface IUserService {
void saveUser();
void deleteUser();
void findAllUser();
}
//實(shí)現(xiàn)類(lèi)
public class UserServiceImpl implements IUserService {
//核心數(shù)據(jù)成員
//日志操作對(duì)象
//權(quán)限管理對(duì)象
//事務(wù)控制對(duì)象
@Override
public void saveUser() {
//權(quán)限驗(yàn)證(假設(shè)權(quán)限驗(yàn)證丟在這里)
//事務(wù)控制
//日志操作
//進(jìn)行Dao層操作
userDao.saveUser();
}
@Override
public void deleteUser() {
}
@Override
public void findAllUser() {
}
}
上述代碼中我們注意到一些問(wèn)題,權(quán)限,日志,事務(wù)都不是用戶管理的核心業(yè)務(wù),也就是說(shuō)用戶管理模塊除了要處理自身的核心業(yè)務(wù)外,還需要處理權(quán)限,日志,事務(wù)等待這些雜七雜八的不相干業(yè)務(wù)的外圍操作,而且這些外圍操作同樣會(huì)在其他業(yè)務(wù)模塊中出現(xiàn),這樣就會(huì)造成如下問(wèn)題
- 代碼混亂:核心業(yè)務(wù)模塊可能需要兼顧處理其他不相干的業(yè)務(wù)外圍操作,這些外圍操作可能會(huì)混亂核心操作的代碼,而且當(dāng)外圍模塊有重大修改時(shí)也會(huì)影響到核心模塊,這顯然是不合理的。
- 代碼分散和冗余:同樣的功能代碼,在其他的模塊幾乎隨處可見(jiàn),導(dǎo)致代碼分散并且冗余度高。
-
代碼質(zhì)量低擴(kuò)展難:由于不太相關(guān)的業(yè)務(wù)代碼混雜在一起,無(wú)法專(zhuān)注核心業(yè)務(wù)代碼,當(dāng)進(jìn)行類(lèi)似無(wú)關(guān)業(yè)務(wù)擴(kuò)展時(shí)又會(huì)直接涉及到核心業(yè)務(wù)的代碼,導(dǎo)致拓展性低。
顯然前面分析的兩種解決方案已束手無(wú)策了,那么該如何解決呢?事實(shí)上我們知道諸如日志,權(quán)限,事務(wù),性能監(jiān)測(cè)等業(yè)務(wù)幾乎涉及到了所有的核心模塊,如果把這些特殊的業(yè)務(wù)代碼直接到核心業(yè)務(wù)模塊的代碼中就會(huì)造成上述的問(wèn)題,而工程師更希望的是這些模塊可以實(shí)現(xiàn)熱插拔特性而且無(wú)需把外圍的代碼入侵到核心模塊中,這樣在日后的維護(hù)和擴(kuò)展也將會(huì)有更佳的表現(xiàn),假設(shè)現(xiàn)在我們把日志、權(quán)限、事務(wù)、性能監(jiān)測(cè)等外圍業(yè)務(wù)看作單獨(dú)的關(guān)注點(diǎn)(也可以理解為單獨(dú)的模塊),每個(gè)關(guān)注點(diǎn)都可以在需要它們的時(shí)刻及時(shí)被運(yùn)用而且無(wú)需提前整合到核心模塊中,這種形式相當(dāng)下圖:
20170215092953013.png
從圖可以看出,每個(gè)關(guān)注點(diǎn)與核心業(yè)務(wù)模塊分離,作為單獨(dú)的功能,橫切幾個(gè)核心業(yè)務(wù)模塊,這樣的做的好處是顯而易見(jiàn)的,每份功能代碼不再單獨(dú)入侵到核心業(yè)務(wù)類(lèi)的代碼中,即核心模塊只需關(guān)注自己相關(guān)的業(yè)務(wù),當(dāng)需要外圍業(yè)務(wù)(日志,權(quán)限,性能監(jiān)測(cè)、事務(wù)控制)時(shí),這些外圍業(yè)務(wù)會(huì)通過(guò)一種特殊的技術(shù)自動(dòng)應(yīng)用到核心模塊中,這些關(guān)注點(diǎn)有個(gè)特殊的名稱,叫做“橫切關(guān)注點(diǎn)”,上圖也很好的表現(xiàn)出這個(gè)概念,另外這種抽象級(jí)別的技術(shù)就叫AOP(面向切面編程),正如上圖所展示的橫切核心模塊的整面,因此AOP的概念就出現(xiàn)了,而所謂的特殊技術(shù)也就面向切面編程的實(shí)現(xiàn)技術(shù),AOP的實(shí)現(xiàn)技術(shù)有多種,其中與Java無(wú)縫對(duì)接的是一種稱為AspectJ的技術(shù)。那么這種切面技術(shù)(AspectJ)是如何在Java中的應(yīng)用呢?不必?fù)?dān)心,也不必全面了解AspectJ,對(duì)于AspectJ,我們只會(huì)進(jìn)行簡(jiǎn)單的了解,從而為理解Spring中的AOP打下良好的基礎(chǔ)(Spring AOP 與AspectJ 實(shí)現(xiàn)原理上并不完全一致,但功能上是相似的),畢竟Spring中已實(shí)現(xiàn)AOP主要功能,開(kāi)發(fā)中直接使用Spring中提供的AOP功能即可,除非我們想單獨(dú)使用AspectJ的其他功能。這里還需要注意的是,AOP的出現(xiàn)確實(shí)解決外圍業(yè)務(wù)代碼與核心業(yè)務(wù)代碼分離的問(wèn)題,但它并不會(huì)替代OOP,如果說(shuō)OOP的出現(xiàn)是把編碼問(wèn)題進(jìn)行模塊化,那么AOP就是把涉及到眾多模塊的某一類(lèi)問(wèn)題進(jìn)行統(tǒng)一管理,因此在實(shí)際開(kāi)發(fā)中AOP和OOP同時(shí)存在并不奇怪,后面將會(huì)慢慢體會(huì)帶這點(diǎn),好的,讓我們開(kāi)始AspectJ吧。
二、AspectJ-AOP的領(lǐng)跑者
2.1環(huán)境搭建
首先通過(guò)maven倉(cāng)庫(kù)下載工具包aspectjtools-1.8.9.jar,該工具包包含ajc核心編譯器,然后打開(kāi)idea檢查是否已安裝aspectJ的插件:

如果使用maven開(kāi)發(fā)(否則在libs目錄自行引入jar)則在pom文件中添加aspectJ的核心依賴包,包含了AspectJ運(yùn)行時(shí)的核心庫(kù)文件:
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.5.4</version>
</dependency>
新建文件處創(chuàng)建aspectJ文件,然后就可以像運(yùn)行java文件一樣,操作aspect文件了。
2.2簡(jiǎn)單示例
編寫(xiě)一個(gè)HelloWord的類(lèi),然后利用AspectJ技術(shù)切入該類(lèi)的執(zhí)行過(guò)程。
public class HelloWorld {
public void sayHello(){
System.out.println("hello world!");
}
public static void main(String[] args) {
HelloWorld helloWorld = new HelloWorld();
helloWorld.sayHello();
}
}
編寫(xiě)AspectJ類(lèi),注意關(guān)鍵字為aspect(MyAspectJDemo.aj,其中aj為AspectJ的后綴),含義與class相同,即定義一個(gè)AspectJ的類(lèi)
public aspect MyAspectJDemo {
/**
* 定義切點(diǎn),日志記錄切點(diǎn)
*/
pointcut recordLog():call(* HelloWorld.sayHello(..));
/**
* 定義切點(diǎn),權(quán)限驗(yàn)證
*/
pointcut authCheck():call(* HelloWorld.sayHello(..));
/**
* 定義前置通知
*/
before():authCheck(){
System.out.println("sayHello方法執(zhí)行前驗(yàn)證權(quán)限");
}
/**
* 定義后置通知
*/
after():recordLog(){
System.out.println("sayHello方法執(zhí)行后記錄日志");
}
}
運(yùn)行helloworld的main函數(shù):
我們發(fā)現(xiàn),明明只運(yùn)行了main函數(shù),卻在sayHello函數(shù)運(yùn)行前后分別進(jìn)行了權(quán)限驗(yàn)證和日志記錄,事實(shí)上這就是AspectJ的功勞了。對(duì)aspectJ有了感性的認(rèn)識(shí)后,再來(lái)聊聊aspectJ到底是什么?AspectJ是一個(gè)java實(shí)現(xiàn)的AOP框架,它能夠?qū)ava代碼進(jìn)行AOP編譯(一般在編譯期進(jìn)行),讓java代碼具有AspectJ的AOP功能(當(dāng)然需要特殊的編譯器),可以這樣說(shuō)AspectJ是目前實(shí)現(xiàn)AOP框架中最成熟,功能最豐富的語(yǔ)言,更幸運(yùn)的是,AspectJ與java程序完全兼容,幾乎是無(wú)縫關(guān)聯(lián),因此對(duì)于有java編程基礎(chǔ)的工程師,上手和使用都非常容易。在案例中,我們使用aspect關(guān)鍵字定義了一個(gè)類(lèi),這個(gè)類(lèi)就是一個(gè)切面,它可以是單獨(dú)的日志切面(功能),也可以是權(quán)限切面或者其他,在切面內(nèi)部使用了pointcut定義了兩個(gè)切點(diǎn),一個(gè)用于權(quán)限驗(yàn)證,一個(gè)用于日志記錄,而所謂的切點(diǎn)就是那些需要應(yīng)用切面的方法,如需要在sayHello方法執(zhí)行前后進(jìn)行權(quán)限驗(yàn)證和日志記錄,那么就需要捕捉該方法,而pointcut就是定義這些需要捕捉的方法(常常是不止一個(gè)方法的),這些方法也稱為目標(biāo)方法,最后還定義了兩個(gè)通知,通知就是那些需要在目標(biāo)方法前后執(zhí)行的函數(shù),如before()即前置通知在目標(biāo)方法之前執(zhí)行,即在sayHello()方法執(zhí)行前進(jìn)行權(quán)限驗(yàn)證,另一個(gè)是after()即后置通知,在sayHello()之后執(zhí)行,如進(jìn)行日志記錄。到這里也就可以確定,切面就是切點(diǎn)和通知的組合體,組成一個(gè)單獨(dú)的結(jié)構(gòu)供后續(xù)使用
簡(jiǎn)單說(shuō)明一下切點(diǎn)的定義語(yǔ)法:關(guān)鍵字為pointcut,定義切點(diǎn),后面跟著函數(shù)名稱,最后編寫(xiě)匹配表達(dá)式,此時(shí)函數(shù)一般使用call()或者execution()進(jìn)行匹配,這里我們統(tǒng)一使用call()
pointcut 函數(shù)名 : 匹配表達(dá)式
關(guān)于定義通知的語(yǔ)法:首先通知有5種類(lèi)型分別如下:
- before 目標(biāo)方法執(zhí)行前執(zhí)行,前置通知
- after 目標(biāo)方法執(zhí)行后執(zhí)行,后置通知
- after returning 目標(biāo)方法返回時(shí)執(zhí)行 ,后置返回通知
- after throwing 目標(biāo)方法拋出異常時(shí)執(zhí)行 異常通知
- around 在目標(biāo)函數(shù)執(zhí)行中執(zhí)行,可控制目標(biāo)函數(shù)是否執(zhí)行,環(huán)繞通知
語(yǔ)法:
[返回值類(lèi)型] 通知函數(shù)名稱(參數(shù)) [returning/throwing 表達(dá)式]:連接點(diǎn)函數(shù)(切點(diǎn)函數(shù)){
函數(shù)體
}
案例如下,其中要注意around通知即環(huán)繞通知,可以通過(guò)proceed()方法控制目標(biāo)函數(shù)是否執(zhí)行。
/**
* 定義前置通知
*
* before(參數(shù)):連接點(diǎn)函數(shù){
* 函數(shù)體
* }
*/
before():authCheck(){
System.out.println("sayHello方法執(zhí)行前驗(yàn)證權(quán)限");
}
/**
* 定義后置通知
* after(參數(shù)):連接點(diǎn)函數(shù){
* 函數(shù)體
* }
*/
after():recordLog(){
System.out.println("sayHello方法執(zhí)行后記錄日志");
}
/**
* 定義后置通知帶返回值
* after(參數(shù))returning(返回值類(lèi)型):連接點(diǎn)函數(shù){
* 函數(shù)體
* }
*/
after()returning(int x): get(){
System.out.println("返回值為:"+x);
}
/**
* 異常通知
* after(參數(shù)) throwing(返回值類(lèi)型):連接點(diǎn)函數(shù){
* 函數(shù)體
* }
*/
after() throwing(Exception e):sayHello2(){
System.out.println("拋出異常:"+e.toString());
}
/**
* 環(huán)繞通知 可通過(guò)proceed()控制目標(biāo)函數(shù)是否執(zhí)行
* Object around(參數(shù)):連接點(diǎn)函數(shù){
* 函數(shù)體
* Object result=proceed();//執(zhí)行目標(biāo)函數(shù)
* return result;
* }
*/
Object around():aroundAdvice(){
System.out.println("sayAround 執(zhí)行前執(zhí)行");
Object result=proceed();//執(zhí)行目標(biāo)函數(shù)
System.out.println("sayAround 執(zhí)行后執(zhí)行");
return result;
}
切入點(diǎn)(pointcut)和通知(advice)的概念已比較清晰,而切面則是定義切入點(diǎn)和通知的組合如上述使用aspect關(guān)鍵字定義的MyAspectJDemo,把切面應(yīng)用到目標(biāo)函數(shù)的過(guò)程稱為織入(weaving)。在前面定義的HelloWord類(lèi)中除了sayHello函數(shù)外,還有main函數(shù),以后可能還會(huì)定義其他函數(shù),而這些函數(shù)都可以稱為目標(biāo)函數(shù),也就是說(shuō)這些函數(shù)執(zhí)行前后也都可以切入通知的代碼,這些目標(biāo)函數(shù)統(tǒng)稱為連接點(diǎn),切入點(diǎn)(pointcut)的定義正是從這些連接點(diǎn)中過(guò)濾出來(lái)的,下圖協(xié)助理解。
2.3 AspectJ的織入方式及其原理概要
經(jīng)過(guò)前面的簡(jiǎn)單介紹,我們已初步掌握了AspectJ的一些語(yǔ)法和概念,但這樣仍然是不夠的,我們?nèi)孕枰私釧spectJ應(yīng)用到j(luò)ava代碼的過(guò)程(這個(gè)過(guò)程稱為織入),對(duì)于織入這個(gè)概念,可以簡(jiǎn)單理解為aspect(切面)應(yīng)用到目標(biāo)函數(shù)(類(lèi))的過(guò)程。對(duì)于這個(gè)過(guò)程,一般分為動(dòng)態(tài)織入和靜態(tài)織入,動(dòng)態(tài)織入的方式是在運(yùn)行時(shí)動(dòng)態(tài)將要增強(qiáng)的代碼織入到目標(biāo)類(lèi)中,這樣往往是通過(guò)動(dòng)態(tài)代理技術(shù)完成的,如Java JDK的動(dòng)態(tài)代理(Proxy,底層通過(guò)反射實(shí)現(xiàn))或者CGLIB的動(dòng)態(tài)代理(底層通過(guò)繼承實(shí)現(xiàn)),Spring AOP采用的就是基于運(yùn)行時(shí)增強(qiáng)的代理技術(shù),這點(diǎn)后面會(huì)分析,這里主要重點(diǎn)分析一下靜態(tài)織入,ApectJ采用的就是靜態(tài)織入的方式。ApectJ主要采用的是編譯期織入,在這個(gè)期間使用AspectJ的acj編譯器(類(lèi)似javac)把a(bǔ)spect類(lèi)編譯成class字節(jié)碼后,在java目標(biāo)類(lèi)編譯時(shí)織入,即先編譯aspect類(lèi)再編譯目標(biāo)類(lèi)。
關(guān)于ajc編譯器,是一種能夠識(shí)別aspect語(yǔ)法的編譯器,它是采用java語(yǔ)言編寫(xiě)的,由于javac并不能識(shí)別aspect語(yǔ)法,便有了ajc編譯器,注意ajc編譯器也可編譯java文件。為了更直觀了解aspect的織入方式,我們打開(kāi)前面案例中已編譯完成的HelloWord.class文件,反編譯后的java代碼如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.gaara.aspectJ;
import org.aspectj.lang.NoAspectBoundException;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class MyAspectJDemo {
static {
try {
ajc$postClinit();
} catch (Throwable var1) {
ajc$initFailureCause = var1;
}
}
public MyAspectJDemo() {
}
@Before(
value = "authCheck()",
argNames = ""
)
public void ajc$before$com_gaara_aspectJ_MyAspectJDemo$1$22c5541() {
System.out.println("sayHello方法執(zhí)行前驗(yàn)證權(quán)限");
}
@After(
value = "recordLog()",
argNames = ""
)
public void ajc$after$com_gaara_aspectJ_MyAspectJDemo$2$4d789574() {
System.out.println("sayHello方法執(zhí)行后記錄日志");
}
public static MyAspectJDemo aspectOf() {
if (ajc$perSingletonInstance == null) {
throw new NoAspectBoundException("com_gaara_aspectJ_MyAspectJDemo", ajc$initFailureCause);
} else {
return ajc$perSingletonInstance;
}
}
public static boolean hasAspect() {
return ajc$perSingletonInstance != null;
}
}
AspectJ的織入原理已很明朗了,當(dāng)然除了編譯期織入,還存在鏈接期(編譯后)織入,即將aspect類(lèi)和java目標(biāo)類(lèi)同時(shí)編譯成字節(jié)碼文件后,再進(jìn)行織入處理,這種方式比較有助于已編譯好的第三方j(luò)ar和Class文件進(jìn)行織入操作,掌握以上AspectJ知識(shí)點(diǎn)就足以協(xié)助理解Spring AOP了。
三、Spring AOP
3.1 簡(jiǎn)單示例
Spring AOP 與ApectJ 的目的一致,都是為了統(tǒng)一處理橫切業(yè)務(wù),但與AspectJ不同的是,Spring AOP 并不嘗試提供完整的AOP功能(即使它完全可以實(shí)現(xiàn)),Spring AOP 更注重的是與Spring IOC容器的結(jié)合,并結(jié)合該優(yōu)勢(shì)來(lái)解決橫切業(yè)務(wù)的問(wèn)題,因此在AOP的功能完善方面,相對(duì)來(lái)說(shuō)AspectJ具有更大的優(yōu)勢(shì),同時(shí),Spring注意到AspectJ在AOP的實(shí)現(xiàn)方式上依賴于特殊編譯器(ajc編譯器),因此Spring很機(jī)智回避了這點(diǎn),轉(zhuǎn)向采用動(dòng)態(tài)代理技術(shù)的實(shí)現(xiàn)原理來(lái)構(gòu)建Spring AOP的內(nèi)部機(jī)制(動(dòng)態(tài)織入),這是與AspectJ(靜態(tài)織入)最根本的區(qū)別。在AspectJ 1.5后,引入@Aspect形式的注解風(fēng)格的開(kāi)發(fā),Spring也非??斓馗M(jìn)了這種方式,因此Spring 2.0后便使用了與AspectJ一樣的注解。請(qǐng)注意,Spring 只是使用了與 AspectJ 5 一樣的注解,但仍然沒(méi)有使用 AspectJ 的編譯器,底層依是動(dòng)態(tài)代理技術(shù)的實(shí)現(xiàn),因此并不依賴于 AspectJ 的編譯器。下面我們先通過(guò)一個(gè)簡(jiǎn)單的案例來(lái)演示Spring AOP的入門(mén)程序
接口類(lèi):
public interface UserDao {
int addUser();
void updateUser();
void deleteUser();
void findUser();
}
實(shí)現(xiàn)類(lèi):
@Repository
public class UserDaoImpl implements UserDao{
public int addUser() {
System.out.println("add user ......");
return 6666;
}
public void updateUser() {
System.out.println("update user ......");
}
public void deleteUser() {
System.out.println("delete user ......");
}
public void findUser() {
System.out.println("find user ......");
}
}
aspect類(lèi):
@Aspect
public class MyAspect {
/**
* 前置通知
*/
@Before("execution(* com.gaara.aop.dao.UserDao.addUser(..))")
public void before(){
System.out.println("前置通知....");
}
/**
* 后置通知
* returnVal,切點(diǎn)方法執(zhí)行后的返回值
*/
@AfterReturning(value = "execution(* com.gaara.aop.dao.UserDao.addUser(..))", returning = "returnVal")
public void afterReturning(Object returnVal){
System.out.println("后置通知...."+returnVal);
}
/**
* 環(huán)繞通知
* @param joinPoint 可用于執(zhí)行切點(diǎn)的類(lèi)
* @return
* @throws Throwable
*/
@Around("execution(* com.gaara.aop.dao.UserDao.addUser(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("環(huán)繞通知前....");
Object obj= joinPoint.proceed();
System.out.println("環(huán)繞通知后....");
return obj;
}
/**
* 拋出通知
* @param e
*/
@AfterThrowing(value = "execution(* com.gaara.aop.dao.UserDao.addUser(..))", throwing = "e")
public void afterThrowable(Throwable e){
System.out.println("出現(xiàn)異常:msg="+e.getMessage());
}
/**
* 無(wú)論什么情況下都會(huì)執(zhí)行的方法
*/
@After(value = "execution(* com.gaara.aop.dao.UserDao.addUser(..))")
public void after(){
System.out.println("最終通知....");
}
}
conf:
@Configuration
@EnableAspectJAutoProxy
public class ConfigOfAOP {
@Bean
public UserDao userDao(){
return new UserDaoImpl();
}
@Bean
public MyAspect myAspect(){
return new MyAspect();
}
}
測(cè)試類(lèi):
public class UserDaoAspectJTest {
AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(ConfigOfAOP.class);
@Test
public void aspectJTest(){
UserDao userDao = app.getBean(UserDao.class);
userDao.addUser();
}
}
簡(jiǎn)單說(shuō)明一下,定義了一個(gè)目標(biāo)類(lèi)UserDaoImpl,利用Spring2.0引入的aspect注解開(kāi)發(fā)功能定義aspect類(lèi)即MyAspect,在該aspect類(lèi)中,編寫(xiě)了5種注解類(lèi)型的通知函數(shù),分別是前置通知@Before、后置通知@AfterReturning、環(huán)繞通知@Around、異常通知@AfterThrowing、最終通知@After,這5種通知與前面分析AspectJ的通知類(lèi)型幾乎是一樣的,并注解通知上使用execution關(guān)鍵字定義的切點(diǎn)表達(dá)式,即指明該通知要應(yīng)用的目標(biāo)函數(shù),當(dāng)只有一個(gè)execution參數(shù)時(shí),value屬性可以省略,當(dāng)含兩個(gè)以上的參數(shù),value必須注明,如存在返回值時(shí)。當(dāng)然除了把切點(diǎn)表達(dá)式直接傳遞給通知注解類(lèi)型外,還可以使用@pointcut來(lái)定義切點(diǎn)匹配表達(dá)式,這個(gè)與AspectJ使用關(guān)鍵字pointcut是一樣的,后面分析。
運(yùn)行程序,結(jié)果符合預(yù)期:

3.2 AOP 術(shù)語(yǔ)
AOP 領(lǐng)域中的術(shù)語(yǔ):
- 通知(Advice): AOP 框架中的增強(qiáng)處理。通知描述了切面何時(shí)執(zhí)行以及如何執(zhí)行增強(qiáng)處理。
- 連接點(diǎn)(join point): 連接點(diǎn)表示應(yīng)用執(zhí)行過(guò)程中能夠插入切面的一個(gè)點(diǎn),這個(gè)點(diǎn)可以是方法的調(diào)用、異常的拋出。在 Spring AOP 中,連接點(diǎn)總是方法的調(diào)用。
- 切點(diǎn)(PointCut): 可以插入增強(qiáng)處理的連接點(diǎn)。
- 切面(Aspect): 切面是通知和切點(diǎn)的結(jié)合。
- 引入(Introduction):引入允許我們向現(xiàn)有的類(lèi)添加新的方法或者屬性。
- 織入(Weaving): 將增強(qiáng)處理添加到目標(biāo)對(duì)象中,并創(chuàng)建一個(gè)被增強(qiáng)的對(duì)象,這個(gè)過(guò)程就是織入。
3.3 定義切入點(diǎn)函數(shù)
在案例中,定義過(guò)濾切入點(diǎn)函數(shù)時(shí),是直接把execution已定義匹配表達(dá)式作為值傳遞給通知類(lèi)型的如下:
@After(value = "execution(* com.gaara.aop.dao.UserDao.addUser(..))")
public void after(){
System.out.println("最終通知....");
}
除了上述方式外,還可采用與ApectJ中使用pointcut關(guān)鍵字類(lèi)似的方式定義切入點(diǎn)表達(dá)式如下,使用@Pointcut注解:
/**
* 使用Pointcut定義切點(diǎn)
*/
@Pointcut("execution(* com.gaara.aop.dao.UserDao.addUser(..))")
private void myPointcut(){}
/**
* 應(yīng)用切入點(diǎn)函數(shù)
*/
@After(value="myPointcut()")
public void after(){
System.out.println("最終通知....");
}
3.4 切入點(diǎn)指示符
為了方法通知應(yīng)用到相應(yīng)過(guò)濾的目標(biāo)方法上,SpringAOP提供了匹配表達(dá)式,這些表達(dá)式也叫切入點(diǎn)指示符,在前面的案例中,它們已多次出現(xiàn)。
3.4.1 通配符
在定義匹配表達(dá)式時(shí),通配符幾乎隨處可見(jiàn),如*、.. 、+ ,它們的含義如下:
- .. :匹配方法定義中的任意數(shù)量的參數(shù),此外還匹配類(lèi)定義中的任意數(shù)量包
//任意返回值,任意名稱,任意參數(shù)的公共方法
execution(public * *(..))
//匹配com.gaara.aop.dao包及其子包中所有類(lèi)中的所有方法
within(com.gaara.aop.dao..*)
- + :匹配給定類(lèi)的任意子類(lèi)
//匹配實(shí)現(xiàn)了DaoUser接口的所有子類(lèi)的方法
within(com.gaara.aop.dao.DaoUser+)
- * :匹配任意數(shù)量的字符
//匹配com.gaara.service包及其子包中所有類(lèi)的所有方法
within(com.gaara.service..*)
//匹配以set開(kāi)頭,參數(shù)為int類(lèi)型,任意返回值的方法
execution(* set*(int))
3.4.2 類(lèi)型簽名表達(dá)式
為了方便類(lèi)型(如接口、類(lèi)名、包名)過(guò)濾方法,Spring AOP 提供了within關(guān)鍵字。其語(yǔ)法格式如下:
within(<type name>)
type name 則使用包名或者類(lèi)名替換即可,來(lái)點(diǎn)案例吧。
//匹配com.gaara.aop.dao包及其子包中所有類(lèi)中的所有方法
@Pointcut("within(com.gaara.aop.dao..*)")
//匹配UserDaoImpl類(lèi)中所有方法
@Pointcut("within(com.gaara.aop.dao.UserDaoImpl)")
//匹配UserDaoImpl類(lèi)及其子類(lèi)中所有方法
@Pointcut("within(com.gaara.aop.dao.UserDaoImpl+)")
//匹配所有實(shí)現(xiàn)UserDao接口的類(lèi)的所有方法
@Pointcut("within(com.gaara.aop.dao.UserDao+)")
3.4.3 方法簽名表達(dá)式
如果想根據(jù)方法簽名進(jìn)行過(guò)濾,關(guān)鍵字execution可以幫到我們,語(yǔ)法表達(dá)式如下
//scope :方法作用域,如public,private,protect
//returnt-type:方法返回值類(lèi)型
//fully-qualified-class-name:方法所在類(lèi)的完全限定名稱
//parameters 方法參數(shù)
execution(<scope> <return-type> <fully-qualified-class-name>.*(parameters))
對(duì)于給定的作用域、返回值類(lèi)型、完全限定類(lèi)名以及參數(shù)匹配的方法將會(huì)應(yīng)用切點(diǎn)函數(shù)指定的通知,這里給出模型案例:
//匹配UserDaoImpl類(lèi)中的所有方法
@Pointcut("execution(* com.gaara.aop.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl類(lèi)中的所有公共的方法
@Pointcut("execution(public * com.gaara.aop.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl類(lèi)中的所有公共方法并且返回值為int類(lèi)型
@Pointcut("execution(public int com.gaara.aop.dao.UserDaoImpl.*(..))")
//匹配UserDaoImpl類(lèi)中第一個(gè)參數(shù)為int類(lèi)型的所有公共的方法
@Pointcut("execution(public * com.gaara.aop.dao.UserDaoImpl.*(int , ..))")
3.4.4 其他指示符
- bean:Spring AOP擴(kuò)展的,AspectJ沒(méi)有對(duì)于指示符,用于匹配特定名稱的Bean對(duì)象的執(zhí)行方法;
//匹配名稱中帶有后綴Service的Bean。
@Pointcut("bean(*Service)")
private void myPointcut(){}
- this :用于匹配當(dāng)前AOP代理對(duì)象類(lèi)型的執(zhí)行方法;請(qǐng)注意是AOP代理對(duì)象的類(lèi)型匹配,這樣就可能包括引入接口也類(lèi)型匹配
//匹配了任意實(shí)現(xiàn)了UserDao接口的代理對(duì)象的方法進(jìn)行過(guò)濾
@Pointcut("this(com.gaara.dao.UserDao)")
private void myPointcut(){}
- target :用于匹配當(dāng)前目標(biāo)對(duì)象類(lèi)型的執(zhí)行方法;
//匹配了任意實(shí)現(xiàn)了UserDao接口的目標(biāo)對(duì)象的方法進(jìn)行過(guò)濾
@Pointcut("target(com.gaara.dao.UserDao)")
private void myPointcut(){}
- @within:用于匹配所以持有指定注解類(lèi)型內(nèi)的方法;請(qǐng)注意與within是有區(qū)別的, within是用于匹配指定類(lèi)型內(nèi)的方法執(zhí)行;
//匹配使用了MarkerAnnotation注解的類(lèi)(注意是類(lèi))
@Pointcut("@within(com.gaara.annotation.MarkerAnnotation)")
private void myPointcut(){}
- @annotation: 根據(jù)所應(yīng)用的注解進(jìn)行方法過(guò)濾
//匹配使用了MarkerAnnotation注解的方法(注意是方法)
@Pointcut("@annotation(com.gaara.annotation.MarkerAnnotation)")
private void myPointcut(){}
關(guān)于表達(dá)式指示符就介紹到這,我們主要關(guān)心前面幾個(gè)常用的即可,不常用過(guò)印象即可。這里最后說(shuō)明一點(diǎn),切點(diǎn)指示符可以使用運(yùn)算符語(yǔ)法進(jìn)行表達(dá)式的混編,如and、or、not(或者&&、||、!),如下一個(gè)簡(jiǎn)單例子:
//匹配了任意實(shí)現(xiàn)了UserDao接口的目標(biāo)對(duì)象的方法并且該接口不在com.gaara.dao包及其子包下
@Pointcut("target(com.gaara.spring.springAop.dao.UserDao) !within(com.gaara.dao..*)")
private void myPointcut(){}
//匹配了任意實(shí)現(xiàn)了UserDao接口的目標(biāo)對(duì)象的方法并且該方法名稱為addUser
@Pointcut("target(com.gaara.spring.springAop.dao.UserDao)&&execution(* com.gaara.spring.springAop.dao.UserDao.addUser(..))")
private void myPointcut(){}
3.5 通知函數(shù)以及傳遞參數(shù)
3.5.1 5種通知函數(shù)
通知在前面的aspectJ和Spring AOP的入門(mén)案例已見(jiàn)過(guò)面,在Spring中與AspectJ一樣,通知主要分5種類(lèi)型,分別是前置通知、后置通知、異常通知、最終通知以及環(huán)繞通知,下面分別介紹。
- 前置通知@Before
前置通知通過(guò)@Before注解進(jìn)行標(biāo)注,并可直接傳入切點(diǎn)表達(dá)式的值,該通知在目標(biāo)函數(shù)執(zhí)行前執(zhí)行,注意JoinPoint,是Spring提供的靜態(tài)變量,通過(guò)joinPoint 參數(shù),可以獲取目標(biāo)對(duì)象的信息,如類(lèi)名稱,方法參數(shù),方法名稱等,,該參數(shù)是可選的。
/**
* 前置通知
* @param joinPoint 該參數(shù)可以獲取目標(biāo)對(duì)象的信息,如類(lèi)名稱,方法參數(shù),方法名稱等
*/
@Before("execution(* com.gaara.aop.dao.UserDao.addUser(..))")
public void before(JoinPoint joinPoint){
System.out.println("我是前置通知");
}
- 后置通知@AfterReturning
通過(guò)@AfterReturning注解進(jìn)行標(biāo)注,該函數(shù)在目標(biāo)函數(shù)執(zhí)行完成后執(zhí)行,并可以獲取到目標(biāo)函數(shù)最終的返回值returnVal,當(dāng)目標(biāo)函數(shù)沒(méi)有返回值時(shí),returnVal將返回null,必須通過(guò)returning = “returnVal”注明參數(shù)的名稱而且必須與通知函數(shù)的參數(shù)名稱相同。請(qǐng)注意,在任何通知中這些參數(shù)都是可選的,需要使用時(shí)直接填寫(xiě)即可,不需要使用時(shí),可以完成不用聲明出來(lái)。如下
/**
* 后置通知,不需要參數(shù)時(shí)可以不提供
*/
@AfterReturning(value="execution(* com.gaara.aop.dao.UserDao.*User(..))")
public void AfterReturning(){
System.out.println("我是后置通知...");
}
/**
* 后置通知
* returnVal,切點(diǎn)方法執(zhí)行后的返回值
*/
@AfterReturning(value="execution(* com.gaara.aop.dao.UserDao.*User(..))",returning = "returnVal")
public void AfterReturning(JoinPoint joinPoint,Object returnVal){
System.out.println("我是后置通知...returnVal+"+returnVal);
}
- 異常通知 @AfterThrowing
該通知只有在異常時(shí)才會(huì)被觸發(fā),并由throwing來(lái)聲明一個(gè)接收異常信息的變量,同樣異常通知也用于Joinpoint參數(shù),需要時(shí)加上即可,如下:
/**
* 拋出通知
* @param e 拋出異常的信息
*/
@AfterThrowing(value="execution(* com.gaara.aop.dao.UserDao.addUser(..))",throwing = "e")
public void afterThrowable(Throwable e){
System.out.println("出現(xiàn)異常:msg="+e.getMessage());
}
- 最終通知 @After
該通知有點(diǎn)類(lèi)似于finally代碼塊,只要應(yīng)用了無(wú)論什么情況下都會(huì)執(zhí)行。
/**
* 無(wú)論什么情況下都會(huì)執(zhí)行的方法
* joinPoint 參數(shù)
*/
@After("execution(* com.gaara.aop.dao.UserDao.*User(..))")
public void after(JoinPoint joinPoint) {
System.out.println("最終通知....");
}
- 環(huán)繞通知@Around
環(huán)繞通知既可以在目標(biāo)方法前執(zhí)行也可在目標(biāo)方法之后執(zhí)行,更重要的是環(huán)繞通知可以控制目標(biāo)方法是否指向執(zhí)行,但即使如此,我們應(yīng)該盡量以最簡(jiǎn)單的方式滿足需求,在僅需在目標(biāo)方法前執(zhí)行時(shí),應(yīng)該采用前置通知而非環(huán)繞通知。案例代碼如下第一個(gè)參數(shù)必須是ProceedingJoinPoint,通過(guò)該對(duì)象的proceed()方法來(lái)執(zhí)行目標(biāo)函數(shù),proceed()的返回值就是環(huán)繞通知的返回值。同樣的,ProceedingJoinPoint對(duì)象也是可以獲取目標(biāo)對(duì)象的信息,如類(lèi)名稱,方法參數(shù),方法名稱等等。
@Around("execution(* com.gaara.aop.dao.UserDao.*User(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("我是環(huán)繞通知前....");
//執(zhí)行目標(biāo)函數(shù)
Object obj= joinPoint.proceed();
System.out.println("我是環(huán)繞通知后....");
return obj;
}
3.5.2 通知傳遞參數(shù)
在Spring AOP中,除了execution和bean指示符不能傳遞參數(shù)給通知方法,其他指示符都可以將匹配的方法相應(yīng)參數(shù)或?qū)ο笞詣?dòng)傳遞給通知方法。獲取到匹配的方法參數(shù)后通過(guò)”argNames”屬性指定參數(shù)名。如下,需要注意的是args(指示符)、argNames的參數(shù)名與before()方法中參數(shù)名 必須保持一致即param。
@Before(value="args(param)", argNames="param") //明確指定了
public void before(int param) {
System.out.println("param:" + param);
}
當(dāng)然也可以直接使用args指示符不帶argNames聲明參數(shù),如下:
@Before("execution(public * com.gaara..*.addUser(..)) && args(userId,..)")
public void before(int userId) {
//調(diào)用addUser的方法時(shí)如果與addUser的參數(shù)匹配則會(huì)傳遞進(jìn)來(lái)會(huì)傳遞進(jìn)來(lái)
System.out.println("userId:" + userId);
}
args(userId,..)該表達(dá)式會(huì)保證只匹配那些至少接收一個(gè)參數(shù)而且傳入的類(lèi)型必須與userId一致的方法,記住傳遞的參數(shù)可以簡(jiǎn)單類(lèi)型或者對(duì)象,而且只有參數(shù)和目標(biāo)方法也匹配時(shí)才有會(huì)有值傳遞進(jìn)來(lái)。
3.6 Aspect優(yōu)先級(jí)
在不同的切面中,如果有多個(gè)通知需要在同一個(gè)切點(diǎn)函數(shù)指定的過(guò)濾目標(biāo)方法上執(zhí)行,那些在目標(biāo)方法前執(zhí)行(”進(jìn)入”)的通知函數(shù),最高優(yōu)先級(jí)的通知將會(huì)先執(zhí)行,在執(zhí)行在目標(biāo)方法后執(zhí)行(“退出”)的通知函數(shù),最高優(yōu)先級(jí)會(huì)最后執(zhí)行。而對(duì)于在同一個(gè)切面定義的通知函數(shù)將會(huì)根據(jù)在類(lèi)中的聲明順序執(zhí)行。如下:
package com.gaara.aop.aspect;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class AspectOne {
@Pointcut("execution(* com.gaara.aop.dao.UserDao.deleteUser(..))")
private void myPointcut(){}
@Before("myPointcut()")
public void beforeOne(){
System.out.println("前置通知....執(zhí)行順序1");
}
@Before("myPointcut()")
public void beforeTwo(){
System.out.println("前置通知....執(zhí)行順序2");
}
@AfterReturning(value = "myPointcut()")
public void afterReturningThree(){
System.out.println("后置通知....執(zhí)行順序3");
}
@AfterReturning(value = "myPointcut()")
public void afterReturningFour(){
System.out.println("后置通知....執(zhí)行順序4");
}
}
在同一個(gè)切面中定義多個(gè)通知響應(yīng)同一個(gè)切點(diǎn)函數(shù),執(zhí)行順序?yàn)槁暶黜樞颍? 
如果在不同的切面中定義多個(gè)通知響應(yīng)同一個(gè)切點(diǎn),進(jìn)入時(shí)則優(yōu)先級(jí)高的切面類(lèi)中的通知函數(shù)優(yōu)先執(zhí)行,退出時(shí)則最后執(zhí)行,如下定義AspectOne類(lèi)和AspectTwo類(lèi)并實(shí)現(xiàn)org.springframework.core.Ordered接口,該接口用于控制切面類(lèi)的優(yōu)先級(jí),同時(shí)重寫(xiě)getOrder方法,定制返回值,返回值(int類(lèi)型)越小優(yōu)先級(jí)越大。其中AspectOne返回值為0,AspectTwo返回值為2,顯然AspectOne優(yōu)先級(jí)高于AspectTwo。
package com.gaara.aop.aspect;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.Ordered;
@Aspect
public class AspectOne implements Ordered{
@Pointcut("execution(* com.gaara.aop.dao.UserDao.deleteUser(..))")
private void myPointcut(){}
@Before("myPointcut()")
public void beforeOne(){
System.out.println("前置通知..AspectOne..執(zhí)行順序1");
}
@Before("myPointcut()")
public void beforeTwo(){
System.out.println("前置通知..AspectOne..執(zhí)行順序2");
}
@AfterReturning(value = "myPointcut()")
public void afterReturningThree(){
System.out.println("后置通知..AspectOne..執(zhí)行順序3");
}
@AfterReturning(value = "myPointcut()")
public void afterReturningFour(){
System.out.println("后置通知..AspectOne..執(zhí)行順序4");
}
/**
* 定義優(yōu)先級(jí),值越低,優(yōu)先級(jí)越高
* @return
*/
public int getOrder() {
return 0;
}
}
@Aspect
public class AspectTwo implements Ordered{
@Pointcut("execution(* com.gaara.aop.dao.UserDao.deleteUser(..))")
private void myPointcut(){}
@Before("myPointcut()")
public void beforeOne(){
System.out.println("前置通知..AspectTwo..執(zhí)行順序1");
}
@Before("myPointcut()")
public void beforeTwo(){
System.out.println("前置通知..AspectTwo..執(zhí)行順序2");
}
@AfterReturning(value = "myPointcut()")
public void afterReturningThree(){
System.out.println("后置通知..AspectTwo..執(zhí)行順序3");
}
@AfterReturning(value = "myPointcut()")
public void afterReturningFour(){
System.out.println("后置通知..AspectTwo..執(zhí)行順序4");
}
/**
* 定義優(yōu)先級(jí),值越低,優(yōu)先級(jí)越高
* @return
*/
public int getOrder() {
return 2;
}
}
運(yùn)行結(jié)果如下: 
雖然只演示了前置通知和后置通知,但其他通知也遵循相同的規(guī)則。
四、 Spring AOP的實(shí)現(xiàn)原理概要
SpringAop的實(shí)現(xiàn)原理是基于動(dòng)態(tài)織入技術(shù),而AspectJ則是靜態(tài)織入,而動(dòng)態(tài)代理技術(shù)又分為Java JDK動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理,前者是基于反射技術(shù)實(shí)現(xiàn),后者是基于繼承的機(jī)制實(shí)現(xiàn)。
4.1 JDK動(dòng)態(tài)代理
示例:
// 自定義的接口類(lèi),JDK動(dòng)態(tài)代理的實(shí)現(xiàn)必須有對(duì)應(yīng)的接口類(lèi)
public interface ExInterface {
void execute();
}
// A類(lèi),實(shí)現(xiàn)了ExInterface接口類(lèi)
public class A implements ExInterface{
public void execute() {
System.out.println("執(zhí)行A的execute方法...");
}
}
// 代理類(lèi)的實(shí)現(xiàn)
public class JDKProxy implements InvocationHandler{
// 要被代理的目標(biāo)對(duì)象
private A target;
public JDKProxy(A target) {
this.target = target;
}
/**
* 創(chuàng)建代理類(lèi)
* @return
*/
public ExInterface createProxy(){
return (ExInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
/**
* 調(diào)用被代理類(lèi)(目標(biāo)對(duì)象)的任意方法都會(huì)觸發(fā)invoke方法
* @param proxy 代理類(lèi)
* @param method 被代理類(lèi)的方法
* @param args 被代理類(lèi)的方法參數(shù)
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 過(guò)濾不需要該業(yè)務(wù)的方法
if ("execute".equals(method.getName())){
// 調(diào)用前驗(yàn)證權(quán)限
System.out.println("執(zhí)行前驗(yàn)證用戶權(quán)限...");
// 調(diào)用目標(biāo)對(duì)象的方法
Object result = method.invoke(target, args);
// 記錄日志數(shù)據(jù)
System.out.println("記錄日志并上報(bào)...");
return result;
}else if ("delete".equals(method.getName())){
}
// 如果不需要增強(qiáng)直接執(zhí)行原方法
return method.invoke(target, args);
}
}
//測(cè)試
public void JDKProxyTest(){
A a = new A();
// 創(chuàng)建JDK代理
JDKProxy jdkProxy = new JDKProxy(a);
// 創(chuàng)建代理對(duì)象
ExInterface proxy = jdkProxy.createProxy();
// 執(zhí)行代理對(duì)象方法
proxy.execute();
}
運(yùn)行結(jié)果: 
在A的execute方法里面沒(méi)有任何權(quán)限和日志的代碼,也沒(méi)有直接操作a對(duì)象,相反的只是調(diào)用了proxy代理對(duì)象的方法,最終結(jié)果卻是預(yù)期的,這就是動(dòng)態(tài)代理技術(shù),是不是跟SpringAOP似曾相識(shí),實(shí)際上動(dòng)態(tài)代理的底層是通過(guò)反射技術(shù)來(lái)實(shí)現(xiàn),只要拿到A類(lèi)的class文件和A類(lèi)的實(shí)現(xiàn)接口,很自然就可以生成相同接口的代理類(lèi)并調(diào)用a對(duì)象的方法了,但實(shí)現(xiàn)java動(dòng)態(tài)代理是有先決條件的,改條件是目標(biāo)對(duì)象必須帶接口,如A類(lèi)的接口是ExInterface,通過(guò)ExInterface接口動(dòng)態(tài)代理技術(shù)便可以創(chuàng)建于A類(lèi)類(lèi)型相同的代理對(duì)象。
代理對(duì)象的創(chuàng)建時(shí)通過(guò)Proxy類(lèi)達(dá)到的,Proxy類(lèi)由Java JDK提供,利用Proxy#newProxyInstance方法便可以動(dòng)態(tài)生成代理對(duì)象,底層通過(guò)反射實(shí)現(xiàn),改方法需要3個(gè)參數(shù)
/**
* @param loader 類(lèi)加載器,一般傳遞目標(biāo)對(duì)象(A類(lèi)即被代理的對(duì)象)的類(lèi)加載器
* @param interfaces 目標(biāo)對(duì)象(A)的實(shí)現(xiàn)接口
* @param h 回調(diào)處理句柄
*/
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
創(chuàng)建代理類(lèi)proxy的代碼如下:
public ExInterface createProxy(){
return (ExInterface) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
到此并沒(méi)有結(jié)束,因?yàn)橛薪涌谶€是遠(yuǎn)遠(yuǎn)不夠,代理類(lèi)還需要實(shí)現(xiàn)InvocationHandler接口,也是由JDK提供,代理類(lèi)必須實(shí)現(xiàn)并重寫(xiě)invoke方法,完全可以把InvocationHandler看成一個(gè)回調(diào)函數(shù),Proxy方法創(chuàng)建代理對(duì)象后,調(diào)用用execute方法時(shí),將會(huì)回調(diào)InvocationHandler#invoke方法,因此我們可以在invoke方法中來(lái)控制被代理對(duì)象的方法執(zhí)行,從而在該方法前后動(dòng)態(tài)增加其他需要執(zhí)行的業(yè)務(wù)。
invoke方法有三個(gè)參數(shù):
- Object proxy:生成的代理對(duì)象
- Method method:目標(biāo)對(duì)象的方法,通過(guò)反射調(diào)用
- Object[] args:目標(biāo)對(duì)象方法的參數(shù)
這就是是Java JDK動(dòng)態(tài)代理的代碼實(shí)現(xiàn)過(guò)程,運(yùn)用JDK動(dòng)態(tài)代理,被代理類(lèi),必須已有實(shí)現(xiàn)接口,因?yàn)镴DK提供的Proxy類(lèi)將通過(guò)目標(biāo)對(duì)象的類(lèi)加載器ClassLoader和Interface,以及句柄創(chuàng)建與A類(lèi)擁有相同接口的代理對(duì)象proxy,改代理對(duì)象將擁有接口中的所有方法,同時(shí)代理類(lèi)必須實(shí)現(xiàn)一個(gè)類(lèi)似回調(diào)函數(shù)的InvocationHandler接口并重寫(xiě)改接口中的invoke方法,當(dāng)調(diào)用proxy的每個(gè)方法時(shí),invoke方法將被調(diào)用,利用該特性,可以在invoke方法中對(duì)目標(biāo)對(duì)象方法執(zhí)行的前后動(dòng)態(tài)添加其他外圍業(yè)務(wù)操作,此時(shí)無(wú)需觸及目標(biāo)對(duì)象的任何代碼,也就實(shí)現(xiàn)了外圍業(yè)務(wù)的操作與目標(biāo)對(duì)象完全解耦合的目的。當(dāng)然缺點(diǎn)也很明顯,需要擁有接口,這也就有了后來(lái)的CGLIB動(dòng)態(tài)代理了。
4.2 CGLIB動(dòng)態(tài)代理
通過(guò)CGLIB動(dòng)態(tài)代理實(shí)現(xiàn)上述功能并不要求目標(biāo)對(duì)象擁有接口類(lèi),實(shí)際上CGLIB動(dòng)態(tài)代理是通過(guò)繼承的方式實(shí)現(xiàn)的,因此可以減少?zèng)]必要的接口,下面直接通過(guò)簡(jiǎn)單案例協(xié)助理解
// 被代理的類(lèi)
public class A {
public void execute() {
System.out.println("執(zhí)行A的execute方法...");
}
}
// 代理類(lèi)
public class CGLIBProxy implements MethodInterceptor{
/**
* 被代理的目標(biāo)類(lèi)
*/
private A target;
public CGLIBProxy(A target) {
super();
this.target = target;
}
/**
* 創(chuàng)建代理對(duì)象
* @return
*/
public A createProxy(){
// 使用CGLIB生成代理
// 1.聲明增強(qiáng)類(lèi)實(shí)例,用于生產(chǎn)代理類(lèi)
Enhancer enhancer = new Enhancer();
// 2.設(shè)置被代理類(lèi)字節(jié)碼,CGLIB根據(jù)字節(jié)碼生成被代理類(lèi)的子類(lèi)
enhancer.setSuperclass(target.getClass());
// 3.設(shè)置回調(diào)函數(shù),即一個(gè)方法攔截
enhancer.setCallback(this);
// 創(chuàng)建代理
return (A) enhancer.create();
}
/**
* 回調(diào)函數(shù)
* @param proxy 代理對(duì)象
* @param method 委托類(lèi)方法
* @param args 方法參數(shù)
* @param methodProxy 每個(gè)被代理的方法都對(duì)應(yīng)一個(gè)MethodProxy對(duì)象,
* methodProxy.invokeSuper方法最終調(diào)用委托類(lèi)(目標(biāo)類(lèi))的原始方法
* @return
* @throws Throwable
*/
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 過(guò)濾不需要該業(yè)務(wù)的方法
if ("execute".equals(method.getName())){
// 調(diào)用前驗(yàn)證權(quán)限
System.out.println("執(zhí)行前驗(yàn)證用戶權(quán)限...");
// 調(diào)用目標(biāo)對(duì)象的方法
Object result = methodProxy.invokeSuper(proxy, args);
// 記錄日志數(shù)據(jù)
System.out.println("記錄日志并上報(bào)...");
return result;
}else if ("delete".equals(method.getName())){
}
// 如果不需要增強(qiáng)直接執(zhí)行原方法
return methodProxy.invokeSuper(proxy, args);
}
}
從代碼看被代理的類(lèi)無(wú)需接口即可實(shí)現(xiàn)動(dòng)態(tài)代理,而CGLIBProxy代理類(lèi)需要實(shí)現(xiàn)一個(gè)方法攔截器接口MethodInterceptor并重寫(xiě)intercept方法,類(lèi)似JDk動(dòng)態(tài)代理的InvocationHandler接口,也是理解為回調(diào)函數(shù),同理每次調(diào)用代理對(duì)象的方法時(shí),intercept方法都會(huì)被調(diào)用,利用該方法便可以在運(yùn)行時(shí)對(duì)方法執(zhí)行前后進(jìn)行動(dòng)態(tài)增強(qiáng)。關(guān)于代理對(duì)象創(chuàng)建則通過(guò)Enhancer類(lèi)來(lái)設(shè)置的,Enhancer是一個(gè)用于產(chǎn)生代理對(duì)象的類(lèi),作用類(lèi)似JDK的Proxy類(lèi),因?yàn)镃GLIB底層是通過(guò)繼承實(shí)現(xiàn)的動(dòng)態(tài)代理,因此需要傳遞目標(biāo)對(duì)象的Class,同時(shí)需要設(shè)置一個(gè)回調(diào)函數(shù)對(duì)調(diào)用方法進(jìn)行攔截并進(jìn)行相應(yīng)處理,最后create()創(chuàng)建目標(biāo)對(duì)象的代理對(duì)象,運(yùn)行結(jié)果與前面的JDK動(dòng)態(tài)代理效果相同。
通過(guò)這些我們也應(yīng)該明白SpringAOP確實(shí)是通過(guò)CGLIB或者JDK代理來(lái)動(dòng)態(tài)的生成代理對(duì)象,這個(gè)代理對(duì)象指的就是AOP代理類(lèi),而AOP代理類(lèi)的方法則通過(guò)在目標(biāo)對(duì)象的切點(diǎn)動(dòng)態(tài)地織入增強(qiáng)處理,從而完成了對(duì)目標(biāo)方法的增強(qiáng)。這里并沒(méi)有非常深入去分析這兩種技術(shù),只是演示了SpringAOP底層實(shí)現(xiàn)的最簡(jiǎn)化的模型代碼,SpringAOP內(nèi)部已都實(shí)現(xiàn)了這兩種技術(shù),SpringAOP在使用時(shí)機(jī)上也進(jìn)行自動(dòng)化調(diào)整,當(dāng)有接口時(shí)會(huì)自動(dòng)選擇JDK動(dòng)態(tài)代理技術(shù),如果沒(méi)有則選擇CGLIB技術(shù),當(dāng)然SpringAOP的底層實(shí)現(xiàn)并沒(méi)有這么簡(jiǎn)單,為更簡(jiǎn)便生成代理對(duì)象,SpringAOP內(nèi)部實(shí)現(xiàn)了一個(gè)專(zhuān)注于生成代理對(duì)象的工廠類(lèi),這樣就避免了大量的手動(dòng)編碼,這一點(diǎn)也是十分人性化的,但最核心的還是動(dòng)態(tài)代理技術(shù)。
