AOP 簡介
在學(xué)習(xí) Spring AOP 之前,我們先來了解一下 AOP翎迁。我們都聽過面向?qū)ο缶幊?OOP)祈坠,那么 AOP 到底是什么呢?中文意思我想大家應(yīng)該都是非常的耳熟能詳了瘩例,中文翻譯過來就是面向切面編程
啊胶,當(dāng)然也有些人會(huì)翻譯成面向方面編程,不過還是感覺面向切面編程更好聽點(diǎn)垛贤。來看下官方的定義:
AOP is a programming paradigm that aims to increase modularity by allowing the separation of cross-cutting concerns.
翻譯過來就是焰坪,AOP 是一種編程范式,旨在通過分離橫切關(guān)注點(diǎn)來增加模塊化聘惦。它通過向現(xiàn)有代碼添加其他行為而不修改現(xiàn)有代碼本身來實(shí)現(xiàn)某饰。相反,我們可以分別聲明這個(gè)新代碼和這些新行為。
【櫻木天亥注】面向?qū)ο缶幊痰幕締卧?code>類(class)黔漂,而面向切面編程的基本單元是切面(aspect)
诫尽。
Spring AOP 是什么
看完了上面 AOP 的定義,那么接下來我們來看下什么是 Spring AOP 呢炬守?
Spring AOP 在 Spring 應(yīng)用程序?qū)崿F(xiàn)了面向切面編程牧嫉。在 AOP 中,這些切面實(shí)現(xiàn)了一些關(guān)注點(diǎn)的模塊化劳较,諸如事務(wù)管理
驹止,日志記錄
或跨越多種類型和對(duì)象的安全性
(通常稱為橫切關(guān)注點(diǎn))浩聋。
Spring AOP 的主要術(shù)語
大多數(shù)的技術(shù)都會(huì)形成自己的一套術(shù)語观蜗,Spring AOP 也不例外。而 Spring AOP 最主要的三個(gè)術(shù)語分別是通知(Advice)
衣洁、切點(diǎn)(Pointcut)
和連接點(diǎn)(Join point)
墓捻。如圖,展示了這三者是如何聯(lián)系到一塊的坊夫。
建言(Advice) ?? 通知定義了切面是什么以及何時(shí)使用砖第,是在方法執(zhí)行之前或之后要采取的實(shí)際操作。Spring 的面向切面編程框架在程序執(zhí)行期間調(diào)用的時(shí)間代碼段。
切點(diǎn)(Pointcut) ?? 切點(diǎn)是一個(gè)與連接點(diǎn)相匹配的表達(dá)式,用于確定是否需要執(zhí)行建言(Advice)侈离。切點(diǎn)有助于確定切面所通知的連接點(diǎn)的范圍唤衫,它的定義會(huì)匹配通知所要織入的一個(gè)或多個(gè)連接點(diǎn)。切點(diǎn)使用與連接點(diǎn)匹配的不同類型的表達(dá)式滤馍,在 Spring 框架中使用 Aspect 切點(diǎn)表達(dá)式語言(SpEL)。
連接點(diǎn)(Join Point) ?? 連接點(diǎn)在應(yīng)用程序中是具體的點(diǎn),例如方法執(zhí)行考赛,異常處理,改變對(duì)象的變量值等莉测。
在 Spring AOP 中一個(gè)連接點(diǎn)總是一個(gè)方法的執(zhí)行點(diǎn)颜骤,即只有方法執(zhí)行點(diǎn)。
Spring AOP 其他常用的術(shù)語
- 切面(Aspect) ?? 一個(gè)切面是跨越多個(gè)類的橫切關(guān)注點(diǎn)的模塊化捣卤,比如事務(wù)管理忍抽。切面可以通過 XML 的方式來配置,也可以通過 Spring AspectJ 集成使用 @Aspect 注解來注入切面董朝。切面是通知(Advice)和切點(diǎn)(Pointcut)的結(jié)合梯找。通知和切點(diǎn)共同定義了切面餓的全部內(nèi)容——它是什么,在何時(shí)和何處完成了其功能益涧。
- Introductions ?? introduction 允許向現(xiàn)有的類添加新的方法或?qū)傩孕獯浮?梢詫⑿碌姆椒ê蛯?shí)例變量引入現(xiàn)有類而無需改變它們,為它們提供新的狀態(tài)和行為久免。Spring AOP 允許我們?yōu)?code>目標(biāo)對(duì)象引入新的接口和對(duì)應(yīng)的實(shí)現(xiàn)浅辙。
- Target Object ?? 織入 advice 的目標(biāo)對(duì)象,目標(biāo)對(duì)象也被稱為 Target object阎姥。Spring AOP 是使用運(yùn)行時(shí)代理實(shí)現(xiàn)切面的记舆,因此該對(duì)象始終是代理對(duì)象,指的不是原來的類呼巴,而是織入 advice 后所產(chǎn)生的代理類泽腮。這意味著在運(yùn)行時(shí)創(chuàng)建子類,其中衣赶,覆蓋目標(biāo)方法并根據(jù)配置包含建言(advice)诊赊。
- Weaving ?? 將 aspect 和其他對(duì)象連接起來, 并創(chuàng)建代理對(duì)象(adviced object)的過程。這可以在編譯時(shí)府瞄,加載時(shí)或在運(yùn)行時(shí)完成碧磅,而 Spring AOP 是在運(yùn)行時(shí)執(zhí)行編織。
織入(weaving)
可以在目標(biāo)對(duì)象的幾個(gè)不同的生命周期執(zhí)行:
- 編譯期織入(Compile Time):編譯目標(biāo)類時(shí)會(huì)織入切面遵馆,同時(shí)鲸郊,要求要有一個(gè)特殊的編譯器。
- 類裝載期織入(Classload Time):當(dāng)目標(biāo)類加載到 JVM 中時(shí)會(huì)織入切面货邓,同時(shí)秆撮,要求要有一個(gè)特殊的類裝載器。
- 運(yùn)行期織入(Runtime):在應(yīng)用程序執(zhí)行期間的某些時(shí)間點(diǎn)織入切面换况。AOP 容器將動(dòng)態(tài)生成代理對(duì)象职辨,該代理對(duì)象將在織入切面時(shí)委托給目標(biāo)對(duì)象。
【櫻木天亥注】AspectJ 織入編譯器屬于編譯期織入复隆;AspectJ 5 的裝載期織入(LTW拨匆,load-time weaving)屬于類裝載期織入;Spring AOP 織入切面則屬于運(yùn)行期織入挽拂。
Spring AOP 的優(yōu)點(diǎn)
1惭每、AOP 是非侵入性的
- 允許開發(fā)人員更加專注于業(yè)務(wù)邏輯的開發(fā),而不是受困于橫切關(guān)注點(diǎn)的問題
- 類可以通過切面獲得建言而無需將 Spring AOP 相關(guān)類或接口添加到類中
2亏栈、AOP 是通過純 Java 語言實(shí)現(xiàn)的
- 這意味著我們不需要特殊的編譯單元或特殊的類加載器
3台腥、通過 Spring IOC 容器實(shí)現(xiàn)依賴注入
4、能夠?qū)⒍鄠€(gè)橫切關(guān)注點(diǎn)織入類中而無需調(diào)用這些類的橫切關(guān)注點(diǎn)
5绒北、能夠集中或模塊化橫切關(guān)注點(diǎn)黎侈,使得維持和改變這些切點(diǎn)變得容易維護(hù)
6、提供了 XML 或 @AspectJ 注解的多種靈活的方式來創(chuàng)建切面
7闷游、易于配置
Spring AOP 的缺點(diǎn)
1峻汉、使用基于代理的 AOP贴汪,所以僅支持方法級(jí)別的建言,不支持屬性級(jí)別的建言
2休吠、當(dāng)且僅當(dāng)方法的可見性為 public 時(shí)扳埂,才支持建言(Advice)
- 方法可見性為 private,protected 或者 default 時(shí)不支持建言
3瘤礁、一個(gè)切面不能作為另一個(gè)切面的 advice target 阳懂。
- 如果一個(gè)類被 @Aspect 標(biāo)注, 則這個(gè)類就不能是其他切面的
目標(biāo)對(duì)象
了, 因?yàn)槭褂?@Aspect 后, 這個(gè)類就會(huì)被排除在 auto-proxying 機(jī)制之外。
4柜思、由于性能問題岩调,建言不適用于細(xì)粒度的對(duì)象,只適合粗粒度的對(duì)象赡盘。
建言(advice)的類型
建言(Advice)的類型有一下五種号枕。
- Before advice 在連接點(diǎn)之前執(zhí)行,但是不能阻止執(zhí)行流程進(jìn)入連接點(diǎn)亡脑,即不能人為地在 before advice 代碼中阻止 join point 中代碼的執(zhí)行堕澄。
- After returning advice 在連接點(diǎn)正常執(zhí)行完成后執(zhí)行邀跃,例如霉咨,一個(gè)方法執(zhí)行完成不拋異常則執(zhí)行這個(gè) advice
- After throwing advice 當(dāng)一個(gè)連接點(diǎn)(join point)拋出異常后退出,則執(zhí)行該 advice
- After advice 無論連接點(diǎn)是正常退出還是拋出了異常退出拍屑,都會(huì)執(zhí)行這個(gè) advice
-
Around advice 圍繞連接點(diǎn)的建言途戒,比如方法調(diào)用。這是最常用的 advice僵驰,在 join point 執(zhí)行前和 join point 執(zhí)行后都執(zhí)行的 advice喷斋。它還負(fù)責(zé)選擇是否繼續(xù)加入連接點(diǎn)還是返回自身的返回值,或者拋出異常來加速方法的執(zhí)行蒜茴。
AOP 代理(proxy)
我們都知道星爪,Spring AOP 底層主要有兩種實(shí)現(xiàn),一種是 JDK 動(dòng)態(tài)代理粉私,一種是 CGLIB 動(dòng)態(tài)代理顽腾。而 Spring AOP 代理的實(shí)現(xiàn)是通過 JDK 動(dòng)態(tài)代理來創(chuàng)建一個(gè)具有目標(biāo)類和通知調(diào)用的 Proxy 類,這些類被稱為 AOP 代理類诺核。
那么抄肖,JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理最大不同是什么呢?前者的原理是 JDK 反射窖杀,并且只支持 Java 接口的代理漓摩;后者的原理是繼承(extend)與覆寫(override),因此能支持普通的 Java 類的代理入客。兩種方式都是動(dòng)態(tài)代理管毙,即運(yùn)行時(shí)實(shí)時(shí)生成代理腿椎。但是,由于JVM 的限制夭咬,CGLIB 無法替換被代理類已經(jīng)被載入的字節(jié)碼酥诽,只能生成并載入一個(gè)新的子類作為代理類,被代理類的字節(jié)碼依然存在于JVM 中皱埠。
Spring AOP 示例
兩種聲明方式:
1肮帐、XML 方式
<!-- 使用 XML 方式,啟用 AspectJ 風(fēng)格的 Spring AOP -->
<aop:aspectj-autoproxy />
2边器、Java 配置的方式
//使用 Java 配置的方式训枢,啟用 AspectJ 風(fēng)格的 Spring AOP
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
定義切面(Aspect)
@Component
@Aspect
public class EmployeeAspect {
@Before("execution(* EmployeeManager.getEmployeeById(..))") //point-cut expression
public void logBefore(JoinPoint joinPoint) {
……
}
}
【櫻木天亥注】僅僅使用 @Aspect 注解, 并不能將一個(gè) Java 對(duì)象轉(zhuǎn)換為 Bean, 因此我們還需要使用類似 @Component 之類的注解。
切點(diǎn)表達(dá)式
@Before("execution(* EmployeeManager.getEmployeeById(..))") //point-cut expression
public void logBeforeAdvice(JoinPoint joinPoint) {
……
}
【櫻木天亥注】
1忘巧、execution( EmployeeManager.getEmployeeById(..)) 這個(gè)切點(diǎn)表達(dá)式表示這個(gè)切點(diǎn)將會(huì)匹配 EmployeeManager 類下面所有的 getEmployeeById 方法恒界,而不管它的參數(shù)類型和數(shù)量是什么。
2砚嘴、@Before 表示當(dāng)前的 Advice 是在連接點(diǎn)(Join point)之前執(zhí)行十酣。
連接點(diǎn)(Join point)
//@Component
public class EmployeeManager
{
public EmployeeDTO getEmployeeById(Integer employeeId) {
System.out.println("Method getEmployeeById() called");
return new EmployeeDTO();
}
}
【櫻木天亥注】如果前面定義切面(Aspect)時(shí)沒有添加 @Component 注解,則這里應(yīng)該要加上际长,不然可能不能將一個(gè) Java 對(duì)象轉(zhuǎn)換為 Bean耸采。
聲明建言(Advice)
Advice 與切點(diǎn)表達(dá)式緊密關(guān)聯(lián),會(huì)在匹配的連接點(diǎn)執(zhí)行前工育,執(zhí)行后或者周圍(around)執(zhí)行虾宇。切點(diǎn)表達(dá)式既可以是簡單的一個(gè) pointcut 名字的引用, 又可以是完整的 pointcut 表達(dá)式。這里簡單的舉幾個(gè)例子說明如下如绸。
Around advice
@Around("execution(* EmployeeManager.getEmployeeById(..))")
public Object logAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
……
}
Before advice
@Before("execution(* EmployeeManager.getEmployeeById(..))") //point-cut expression
public void logBeforeAdvice(JoinPoint joinPoint) {
……
}
參考來源
1嘱朽、Introduction to Spring AOP
2、Spring AOP Tutorial Example
3怔接、Spring AOP Example Tutorial – Aspect, Advice, Pointcut, JoinPoint, Annotations, XML Configuration
4搪泳、Overview of Spring Aspect Oriented Programming (AOP)
5、Spring AOP 是什么扼脐?
6岸军、徹底征服 Spring AOP 之 理論篇
7、《Spring實(shí)戰(zhàn)(第 4 版)》