一、AOP 簡(jiǎn)介
AOP(Aspect-Oriented Programming, 面向切面編程): 是一種新的方法論, 是對(duì)傳統(tǒng) OOP(Object-Oriented Programming, 面向?qū)ο缶幊? 的補(bǔ)充.
AOP 的主要編程對(duì)象是切面(aspect), 而切面模塊化橫切關(guān)注點(diǎn).
在應(yīng)用 AOP 編程時(shí), 仍然需要定義公共功能, 但可以明確的定義這個(gè)功能在哪里, 以什么方式應(yīng)用, 并且不必修改受影響的類. 這樣一來(lái)橫切關(guān)注點(diǎn)就被模塊化到特殊的對(duì)象(切面)里.
AOP 的好處:
①窥妇、每個(gè)事物邏輯位于一個(gè)位置, 代碼不分散, 便于維護(hù)和升級(jí)
②、業(yè)務(wù)模塊更簡(jiǎn)潔, 只包含核心業(yè)務(wù)代碼.
二魏身、AOP 術(shù)語(yǔ)
切面(Aspect): 橫切關(guān)注點(diǎn)(跨越應(yīng)用程序多個(gè)模塊的功能)被模塊化的特殊對(duì)象
通知(Advice):切面必須要完成的工作
目標(biāo)(Target):被通知的對(duì)象
代理(Proxy):向目標(biāo)對(duì)象應(yīng)用通知之后創(chuàng)建的對(duì)象
連接點(diǎn)(Joinpoint):程序執(zhí)行的某個(gè)特定位置:如類某個(gè)方法調(diào)用前橱赠、調(diào)用后器联、方法拋出異常后等。連接點(diǎn)由兩個(gè)信息確定:方法表示的程序執(zhí)行點(diǎn)遍烦;相對(duì)點(diǎn)表示的方位俭嘁。例如 ArithmethicCalculator#add() 方法執(zhí)行前的連接點(diǎn),執(zhí)行點(diǎn)為 ArithmethicCalculator#add()服猪; 方位為該方法執(zhí)行前的位置
切點(diǎn)(pointcut):每個(gè)類都擁有多個(gè)連接點(diǎn):例如 ArithmethicCalculator 的所有方法實(shí)際上都是連接點(diǎn)供填,即連接點(diǎn)是程序類中客觀存在的事務(wù)。AOP 通過(guò)切點(diǎn)定位到特定的連接點(diǎn)罢猪。類比:連接點(diǎn)相當(dāng)于數(shù)據(jù)庫(kù)中的記錄近她,切點(diǎn)相當(dāng)于查詢條件。切點(diǎn)和連接點(diǎn)不是一對(duì)一的關(guān)系膳帕,一個(gè)切點(diǎn)匹配多個(gè)連接點(diǎn)粘捎,切點(diǎn)通過(guò) org.springframework.aop.Pointcut 接口進(jìn)行描述,它使用類和方法作為連接點(diǎn)的查詢條件危彩。
AspectJ:Java 社區(qū)里最完整最流行的 AOP 框架.
在 Spring2.0 以上版本中, 可以使用基于 AspectJ 注解或基于 XML 配置的 AOP
三攒磨、在 Spring 中啟用 AspectJ 注解支持
(1)要在 Spring 應(yīng)用中使用 AspectJ 注解, 必須在 classpath 下包含 AspectJ 類庫(kù):
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
(2)將 aop Schema 添加到<beans>根元素中.
(3)要在 Spring IOC 容器中啟用 AspectJ 注解支持, 只要在 Bean 配置文件中定義一個(gè)空的 XML 元素<aop:aspectj-autoproxy>
(4)當(dāng) Spring IOC 容器偵測(cè)到 Bean 配置文件中的 <aop:aspectj-autoproxy>元素時(shí), 會(huì)自動(dòng)為與 AspectJ 切面匹配的 Bean 創(chuàng)建代理.
四、用 AspectJ 注解聲明切面
要在 Spring 中聲明 AspectJ 切面, 只需要在 IOC 容器中將切面聲明為 Bean 實(shí)例. 當(dāng)在 Spring IOC 容器中初始化 AspectJ 切面之后, Spring IOC 容器就會(huì)為那些與 AspectJ 切面相匹配的 Bean 創(chuàng)建代理.
在 AspectJ 注解中, 切面只是一個(gè)帶有 @Aspect 注解的 Java 類.
通知是標(biāo)注有某種注解的簡(jiǎn)單的 Java 方法.
AspectJ 支持 5 種類型的通知注解:
@Before:前置通知, 在方法執(zhí)行之前執(zhí)行
@After:后置通知, 在方法執(zhí)行之后執(zhí)行
@AfterRunning:返回通知, 在方法返回結(jié)果之后執(zhí)行
@AfterThrowing:異常通知, 在方法拋出異常之后
@Around:環(huán)繞通知, 圍繞著方法執(zhí)行
五汤徽、前置通知和后置通知
(1)導(dǎo)入包
(2)新建一個(gè)接口和一個(gè)實(shí)現(xiàn)類
(3)配置自動(dòng)掃描的包
(4)測(cè)試
(5)通過(guò)Aspect聲明切面
(6)在bean的配置文件中配置使AspectJ的注解起作用
(7)測(cè)試是否生效
六娩缰、利用方法簽名編寫 AspectJ 切入點(diǎn)表達(dá)式
最典型的切入點(diǎn)表達(dá)式時(shí)根據(jù)方法的簽名來(lái)匹配各種方法:
execution * com.liqc.spring.ArithmeticCalculator.*(..):匹配 ArithmeticCalculator 中聲明的所有方法,第一個(gè) * 代表任意修飾符及任意返回值,第二個(gè) * 代表任意方法谒府, .. 匹配任意數(shù)量的參數(shù)拼坎,若目標(biāo)類與接口與該切面在同一個(gè)包中梧奢,可以省略包名。
execution public * ArithmeticCalculator.*(..):匹配 ArithmeticCalculator 接口的所有公有方法演痒。
execution public double ArithmeticCalculator.*(..):匹配 ArithmeticCalculator 中返回 double 類型數(shù)值的方法。
execution public double ArithmeticCalculator.*(double, ..):匹配第一個(gè)參數(shù)為 double 類型的方法趋惨, .. 匹配任意數(shù)量任意類型的參數(shù)鸟顺。
execution public double ArithmeticCalculator.*(double, double):匹配參數(shù)類型為 double, double 類型的方法。
合并切入點(diǎn)表達(dá)式:
在 AspectJ 中器虾,切入點(diǎn)表達(dá)式可以通過(guò)操作符 &&讯嫂,||,! 結(jié)合起來(lái)兆沙。
七欧芽、讓通知訪問(wèn)當(dāng)前連接點(diǎn)的細(xì)節(jié)
可以在通知方法中聲明一個(gè)類型為 JoinPoint 的參數(shù),然后就能訪問(wèn)鏈接細(xì)節(jié). 如方法名稱和參數(shù)值葛圃。
八千扔、返回通知
(1)無(wú)論連接點(diǎn)是正常返回還是拋出異常,后置通知都會(huì)執(zhí)行库正, 如果只想在連接點(diǎn)返回的時(shí)候記錄日志曲楚,應(yīng)使用返回通知代替后置通知。
(2)在返回通知中訪問(wèn)連接點(diǎn)的返回值:
在返回通知中褥符,只要將 returning 屬性添加到 @AfterReturning 注解中龙誊,就可以訪問(wèn)連接點(diǎn)的返回值。該屬性的值即為用來(lái)傳入返回值的參數(shù)名稱喷楣。
必須在通知方法的簽名中添加一個(gè)同名參數(shù)趟大,在運(yùn)行時(shí),Spring AOP 會(huì)通過(guò)這個(gè)參數(shù)傳遞返回值铣焊。
原始的切點(diǎn)表達(dá)式需要出現(xiàn)在 pointcut 屬性中逊朽。
九、異常通知
只在連接點(diǎn)拋出異常時(shí)才執(zhí)行異常通知
將 throwing 屬性添加到 @AfterThrowing 注解中, 也可以訪問(wèn)連接點(diǎn)拋出的異常. Throwable 是所有錯(cuò)誤和異常類的超類. 所以在異常通知方法可以捕獲到任何錯(cuò)誤和異常.
如果只對(duì)某種特殊的異常類型感興趣, 可以將參數(shù)聲明為其他異常的參數(shù)類型. 然后通知就只在拋出這個(gè)類型及其子類的異常時(shí)才被執(zhí)行.
十曲伊、環(huán)繞通知
環(huán)繞通知是所有通知類型中功能最為強(qiáng)大的, 能夠全面地控制連接點(diǎn). 甚至可以控制是否執(zhí)行連接點(diǎn).
對(duì)于環(huán)繞通知來(lái)說(shuō), 連接點(diǎn)的參數(shù)類型必須是 ProceedingJoinPoint . 它是 JoinPoint 的子接口, 允許控制何時(shí)執(zhí)行, 是否執(zhí)行連接點(diǎn).
在環(huán)繞通知中需要明確調(diào)用 ProceedingJoinPoint 的 proceed() 方法來(lái)執(zhí)行被代理的方法. 如果忘記這樣做就會(huì)導(dǎo)致通知被執(zhí)行了, 但目標(biāo)方法沒(méi)有被執(zhí)行.
注意: 環(huán)繞通知的方法需要返回目標(biāo)方法執(zhí)行之后的結(jié)果, 即調(diào)用 joinPoint.proceed(); 的返回值, 否則會(huì)出現(xiàn)空指針異常
十一惋耙、指定切面的優(yōu)先級(jí)
在同一個(gè)連接點(diǎn)上應(yīng)用不止一個(gè)切面時(shí), 除非明確指定, 否則它們的優(yōu)先級(jí)是不確定的.
切面的優(yōu)先級(jí)可以通過(guò)實(shí)現(xiàn) Ordered 接口或利用 @Order 注解指定.
實(shí)現(xiàn) Ordered 接口, getOrder() 方法的返回值越小, 優(yōu)先級(jí)越高.
若使用 @Order 注解, 序號(hào)出現(xiàn)在注解中,值越小優(yōu)先級(jí)越高
十二熊昌、重用切入點(diǎn)定義
在編寫 AspectJ 切面時(shí), 可以直接在通知注解中書寫切入點(diǎn)表達(dá)式. 但同一個(gè)切點(diǎn)表達(dá)式可能會(huì)在多個(gè)通知中重復(fù)出現(xiàn).
在 AspectJ 切面中, 可以通過(guò) @Pointcut 注解將一個(gè)切入點(diǎn)聲明成簡(jiǎn)單的方法. 切入點(diǎn)的方法體通常是空的, 因?yàn)閷⑶腥朦c(diǎn)定義與應(yīng)用程序邏輯混在一起是不合理的.
切入點(diǎn)方法的訪問(wèn)控制符同時(shí)也控制著這個(gè)切入點(diǎn)的可見(jiàn)性. 如果切入點(diǎn)要在多個(gè)切面中共用, 最好將它們集中在一個(gè)公共的類中. 在這種情況下, 它們必須被聲明為 public. 在引入這個(gè)切入點(diǎn)時(shí), 必須將類名也包括在內(nèi). 如果類沒(méi)有與這個(gè)切面放在同一個(gè)包中, 還必須包含包名.
其他通知可以通過(guò)方法名稱引入該切入點(diǎn).
十三绽榛、引入通知(只需了解即可)
引入通知是一種特殊的通知類型. 它通過(guò)為接口提供實(shí)現(xiàn)類, 允許對(duì)象動(dòng)態(tài)地實(shí)現(xiàn)接口, 就像對(duì)象已經(jīng)在運(yùn)行時(shí)擴(kuò)展了實(shí)現(xiàn)類一樣.
引入通知可以使用兩個(gè)實(shí)現(xiàn)類 MaxCalculatorImpl 和 MinCalculatorImpl, 讓 ArithmeticCalculatorImpl 動(dòng)態(tài)地實(shí)現(xiàn) MaxCalculator 和 MinCalculator 接口. 而這與從 MaxCalculatorImpl 和 MinCalculatorImpl 中實(shí)現(xiàn)多繼承的效果相同. 但卻不需要修改 ArithmeticCalculatorImpl 的源代碼
引入通知也必須在切面中聲明
在切面中, 通過(guò)為任意字段添加@DeclareParents 注解來(lái)引入聲明.
注解類型的 value 屬性表示哪些類是當(dāng)前引入通知的目標(biāo). value 屬性值也可以是一個(gè) AspectJ 類型的表達(dá)式, 以將一個(gè)即可引入到多個(gè)類中.? defaultImpl 屬性中指定這個(gè)接口使用的實(shí)現(xiàn)類
十四、用基于 XML 的配置聲明切面
除了使用 AspectJ 注解聲明切面, Spring 也支持在 Bean 配置文件中聲明切面. 這種聲明是通過(guò) aop schema 中的 XML 元素完成的.
正常情況下, 基于注解的聲明要優(yōu)先于基于 XML 的聲明. 通過(guò) AspectJ 注解, 切面可以與 AspectJ 兼容, 而基于 XML 的配置則是 Spring 專有的. 由于 AspectJ 得到越來(lái)越多的 AOP 框架支持, 所以以注解風(fēng)格編寫的切面將會(huì)有更多重用的機(jī)會(huì).
(1)聲明切面
當(dāng)使用 XML 聲明切面時(shí), 需要在根元素中導(dǎo)入 aop Schema
在 Bean 配置文件中, 所有的 Spring AOP 配置都必須定義在 <aop:config> 元素內(nèi)部. 對(duì)于每個(gè)切面而言, 都要?jiǎng)?chuàng)建一個(gè)<aop:aspect> 元素來(lái)為具體的切面實(shí)現(xiàn)引用后端 Bean 實(shí)例. 切面 Bean 必須有一個(gè)標(biāo)示符, 供 <aop:aspect> 元素引用
(2)聲明切入點(diǎn)
切入點(diǎn)使用 <aop:pointcut>元素聲明
切入點(diǎn)必須定義在<aop:aspect>元素下, 或者直接定義在 <aop:config> 元素下.
①婿屹、定義在<aop:aspect>元素下: 只對(duì)當(dāng)前切面有效
②灭美、定義在 <aop:config> 元素下: 對(duì)所有切面都有效
基于 XML 的 AOP 配置不允許在切入點(diǎn)表達(dá)式中用名稱引用其他切入點(diǎn).
(3)聲明通知
在 aop Schema 中, 每種通知類型都對(duì)應(yīng)一個(gè)特定的 XML 元素.
通知元素需要使用<pointcut-ref>來(lái)引用切入點(diǎn), 或用 <pointcut> 直接嵌入切入點(diǎn)表達(dá)式. method 屬性指定切面類中通知方法的名稱.
(4)聲明引入
可以利用 <aop:declare-parents> 元素在切面內(nèi)部聲明引入