Spring AOP快速入門及開發(fā)步驟
Spring 框架有兩大核心 IoC,AOP。在前面我們已經(jīng)學(xué)習(xí)過了 IoC 的相關(guān)知識,今天就讓我們開始 AOP 的學(xué)習(xí)。
一、AOP 概述
Aspect Oriented Programming(面向切面編程)。
切面就是指某一類特定問題,所以 AOP 也可以理解為面向特定方法編程。
**AOP 是一種思想,是對某一類事情的集中處理。**Spring AOP 是其中的一種實(shí)現(xiàn)方式。
AOP 的作用:在程序運(yùn)行期間,在不修改源代碼的基礎(chǔ)上,對已有方法進(jìn)行增強(qiáng)(無侵入性:解耦)。
二、Spring AOP 快速入門
我們先通過下面的程序體驗(yàn)下 AOP 的開發(fā),并掌握 Spring 中 AOP 的開發(fā)步驟。
2.1 引入 AOP 依賴:
在 pom.xml 文件中添加配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>2.2 編寫 AOP 程序:
@Aspect
@Slf4j
@Component
public class TestAspect {
@Around("execution(* com.example.demo.controller.*.*(..))")
public Object demo(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("方法執(zhí)行前執(zhí)行");
Object result = joinPoint.proceed();
log.info("方法執(zhí)行后執(zhí)行");
return result;
}
}controller 類:
@RequestMapping("/test")
@RestController
@Slf4j
public class TestController {
@RequestMapping("/t1")
public void test1(){
log.info("我是 test1");
}
}調(diào)用 controller 中的 test1 方法。
結(jié)果如下:

對程序進(jìn)行簡單的講解:
- @Aspect:標(biāo)識這是一個(gè)切面類。
- @Around:環(huán)繞通知,在目標(biāo)方法的前后都會被執(zhí)行。后面的表達(dá)式表示對哪些方法進(jìn)行增強(qiáng)。
- ProceedingJoinPoint.proceed()讓原始方法執(zhí)行。
整個(gè)代碼劃分為三部分。

通過上面的程序,我們也可以感受到 AOP 面向切面編程的一些優(yōu)勢:
- 代碼無侵入:不修改原始的業(yè)務(wù)方法,就可以對原始的業(yè)務(wù)方法進(jìn)行了功能的增強(qiáng)或者是功能的改變。
- 減少了重復(fù)代碼。
- 提高開發(fā)效率。
- 維護(hù)方便。
三、Spring AOP 詳解
3.1 Spring AOP 核心概念:
3.1.1 切點(diǎn)(Pointcut):
切點(diǎn)(Pointcut),也稱之為"切入點(diǎn)"。
Pointcut 的作用就是提供一組規(guī)則(使用 AspectJ pointcut expression language 來描述),告訴程序?qū)δ男┓椒▉磉M(jìn)行功能增強(qiáng)。

上面的表達(dá)式 execution(* com.example.demo.controller..(…)) 就是切點(diǎn)表達(dá)式。
3.1.2 連接點(diǎn)(Join Point):
滿足切點(diǎn)表達(dá)式規(guī)則的方法,就是連接點(diǎn)。也就是可以被 AOP 控制的方法。
切點(diǎn)和連接點(diǎn)的關(guān)系:
連接點(diǎn)是滿足切點(diǎn)表達(dá)式的元素。切點(diǎn)可以看做是保存了眾多連接點(diǎn)的一個(gè)集合。
3.1.3 通知(Advice):
通知就是具體要做的工作,指哪些重復(fù)的邏輯,也就是共性功能(最終體現(xiàn)為一個(gè)方法)。

在 AOP 面向切面編程當(dāng)中,我們把這部分重復(fù)的代碼邏輯抽取出來單獨(dú)定義,這部分代碼就是通知的內(nèi)容。
3.1.4 切面(Aspect):
切面(Aspect)= 切點(diǎn)(Pointcut)+ 通知(Advice)。
通過切面就能夠描述當(dāng)前 AOP 程序需要針對于哪些方法,在什么時(shí)候執(zhí)行什么樣的操作。
切面既包含了通知邏輯的定義,也包括了連接點(diǎn)的定義。

切面所在的類,我們一般稱為切面類(被 @Aspect 注解標(biāo)識的類)。
3.2 通知類型:
上面我們講了什么是通知,接下來學(xué)習(xí)通知的類型。@Around 就是其中一種通知類型,表示環(huán)繞通知。Spring 中 AOP 的通知類型有以下幾種:
- @Around:環(huán)繞通知,此注解標(biāo)注的通知方法在目標(biāo)方法前后都被執(zhí)行。
- @Before:前置通知,此注解標(biāo)注的通知方法在目標(biāo)方法前被執(zhí)行。
- @After:后置通知,此注解標(biāo)注的通知方法在目標(biāo)方法后被執(zhí)行,無論是否有異常都會執(zhí)行。
- @AfterReturning:返回后通知,此注解標(biāo)注的通知方法在目標(biāo)方法返回后被執(zhí)行,有異常不會執(zhí)行。
- @AfterThrowing:異常后通知,此注解標(biāo)注的通知方法發(fā)生異常后執(zhí)行。
沒有異常的運(yùn)行順序:
程序正常運(yùn)行的情況下,@AfterThrowing 標(biāo)識的通知方法不會執(zhí)行。

出現(xiàn)異常的運(yùn)行順序:
@AfterReturning 標(biāo)識的通知方法不會執(zhí)行,@AfterThrowing 標(biāo)識的通知方法執(zhí)行了。
@Around 環(huán)繞通知中原始方法調(diào)用時(shí)有異常,通知中的環(huán)繞后的代碼邏輯也不會再執(zhí)行了(因?yàn)樵挤椒ㄕ{(diào)用出異常了)。

注意:
- @Around 環(huán)繞通知需要調(diào)用 ProceedingJoinPoint.proceed() 來讓原始方法執(zhí)行,其他通知不需要考慮目標(biāo)方法執(zhí)行。
- @Around 環(huán)繞通知方法的返回值,必須指定為 Object,來接收原始方法的返回值,否則原始方法執(zhí)行完畢,是獲取不到返回值的。
- 一個(gè)切面類可以有多個(gè)切點(diǎn)。
3.3 @PointCut:
Spring 提供了 @PointCut 注解,把公共的切點(diǎn)表達(dá)式提取出來,需要用到時(shí)引用該切入點(diǎn)表達(dá)式即可,便于后續(xù)代碼的維護(hù)。
@Aspect
@Slf4j
@Component
public class TestAspect {
@Pointcut("execution(* com.example.demo.controller.*.*(..))")
public void pt(){}
@Around("pt()")
public Object demo(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("方法執(zhí)行前執(zhí)行");
Object result = joinPoint.proceed();
log.info("方法執(zhí)行后執(zhí)行");
return result;
}
}當(dāng)切點(diǎn)定義使用 private 修飾時(shí),僅能在當(dāng)前切面類中使用,當(dāng)其他切面類也要使用當(dāng)前切點(diǎn)定義時(shí),就需要把 private 改為 public。引用方式為:全限定類名.方法名()。
@Slf4j
@Component
@Aspect
public class TestAspect2 {
@Before("com.example.demo.aspect.TestAspect.pt()")
public void doBefore() {
log.info("執(zhí)? TestAspect2 -> Before ?法");
}
}3.4 切面優(yōu)先級 @Order:
當(dāng)我們在一個(gè)項(xiàng)目中,定義了多個(gè)切面類時(shí),并且這些切面類的多個(gè)切入點(diǎn)都匹配到了同一個(gè)目標(biāo)方法。當(dāng)目標(biāo)方法運(yùn)行的時(shí)候,這些切面類中的通知方法都會執(zhí)行,那么這幾個(gè)通知方法的執(zhí)行順序是什么樣的呢?
我們通過程序來進(jìn)行驗(yàn)證。
@Slf4j
@Component
@Aspect
public class TestAspect2 {
@Pointcut("execution(* com.example.demo.controller.*.*(..))")
private void pt(){}
//前置通知
@Before("pt()")
public void doBefore() {
log.info("執(zhí)行 TestAspect2 -> Before 方法");
}
//后置通知
@After("pt()")
public void doAfter() {
log.info("執(zhí)行 TestAspect2 -> After 方法");
}
}
@Aspect
@Component
@Slf4j
public class TestAspect3 {
@Pointcut("execution(* com.example.demo.controller.*.*(..))")
private void pt(){}
//前置通知
@Before("pt()")
public void doBefore() {
log.info("執(zhí)行 TestAspect3 -> Before 方法");
}
//后置通知
@After("pt()")
public void doAfter() {
log.info("執(zhí)行 TestAspect3 -> After 方法");
}
}
@Aspect
@Component
@Slf4j
public class TestAspect4 {
@Pointcut("execution(* com.example.demo.controller.*.*(..))")
private void pt(){}
//前置通知
@Before("pt()")
public void doBefore() {
log.info("執(zhí)行 TestAspect4 -> Before 方法");
}
//后置通知
@After("pt()")
public void doAfter() {
log.info("執(zhí)行 TestAspect4 -> After 方法");
}
}運(yùn)行上面程序:

通過上述程序的運(yùn)行結(jié)果,可以看出:
存在多個(gè)切面類時(shí),默認(rèn)按照切面類的類名字母排序:
- @Before 通知:字母排名靠前的先執(zhí)行。
- @After 通知:字母排名靠前的后執(zhí)行。
但這種方式不方便管理,我們的類名更多還是具備一定含義的。
Spring 給我們提供了一個(gè)新的注解,來控制這些切面通知的執(zhí)行順序:@Order。
@Slf4j
@Component
@Aspect
@Order(10)
public class TestAspect2 {
//代碼省略
}
@Aspect
@Component
@Slf4j
@Order(5)
public class TestAspect3 {
//代碼省略
}
@Aspect
@Component
@Slf4j
@Order(1)
public class TestAspect4 {
//代碼省略
}運(yùn)行程序:

通過上述程序的運(yùn)行結(jié)果,得出結(jié)論:
@Order 注解標(biāo)識的切面類,執(zhí)行順序如下:
- @Before 通知:數(shù)字越小先執(zhí)行。
- @After 通知:數(shù)字越大先執(zhí)行。
@Order 的執(zhí)行順序可以抽象成下面這張圖:

3.5 切點(diǎn)表達(dá)式:
上面的代碼中,我們一直在使用切點(diǎn)表達(dá)式來描述切點(diǎn)。下面我們來介紹一下切點(diǎn)表達(dá)式的語法。
切點(diǎn)表達(dá)式常見有兩種表達(dá)方式:
- execution:根據(jù)方法的簽名來匹配。
- @annotation:根據(jù)注解匹配。
3.5.1 execution 表達(dá)式:
execution() 是最常用的切點(diǎn)表達(dá)式,用來匹配方法,語法為:
execution (<訪問修飾符> <返回類型> <包名.類名.方法(方法參數(shù))> <異常>)
其中:訪問修飾符和異??梢允÷?。

切點(diǎn)表達(dá)式支持通配符表達(dá):
*:匹配任意字符,只匹配一個(gè)元素(返回類型,包,類名,方法或者方法參數(shù))。- 包名使用 * 表示任意包(一層包使用一個(gè) * )。類名使用 * 表示任意類。返回值使用 * 表示任意返回值類型。
- 方法名使用 * 表示任意方法(參數(shù)可能有限制)。
- 參數(shù)使用 * 表示一個(gè)任意類型的參數(shù)。
..:匹配多個(gè)連續(xù)的任意符號,可以通配任意層級的包,或任意類型,任意個(gè)數(shù)的參數(shù)。
- 使用
..配置包名,標(biāo)識此包以及此包下的所有子包。 - 可以使用
..配置參數(shù),任意個(gè)任意類型的參數(shù)。
3.5.2 @annotation:
execution 表達(dá)式更適用有規(guī)則的,如果我們要匹配多個(gè)無規(guī)則的方法呢,比如:TestController 中的 t1() 和 UserController 中的 u1() 這兩個(gè)方法。這個(gè)時(shí)候我們使用 execution 這種切點(diǎn)表達(dá)式來描述就不是很方便了。我們可以借助自定義注解的方式以及另一種切點(diǎn)表達(dá)式 @annotation 來描述這一類的切點(diǎn)。
實(shí)現(xiàn)步驟:
- 編寫自定義注解。
- 使用 @annotation 表達(dá)式來描述切點(diǎn)。
- 在方法上添加自定義注解。
創(chuàng)建一個(gè)注解類(和創(chuàng)建 Class 文件一樣的流程,選擇 Annotation 就可以了)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}使用 @annotation 表達(dá)式來描述切點(diǎn)。
@Component
@Slf4j
@Aspect
public class MyAspectDemo {
@Before("@annotation(com.example.demo.aspect.MyAspect)")
public void doBefore(){
log.info("我是 MyAspectDemo");
}
}在方法上添加自定義注解。
@RequestMapping("/test")
@RestController
@Slf4j
public class TestController {
@MyAspect
@RequestMapping("/t1")
public void test1(){
log.info("我是 test1");
}
}運(yùn)行程序,訪問 test1 方法。

3.6 Spring AOP 的實(shí)現(xiàn)方式(常見面試題):
- 基于注解 @Aspect。
- 基于自定義注解(@annotation)。
- 基于 Spring API(通過 xml 配置的方式,自從 SpringBoot 廣泛使用之后,這種方法幾乎看不到了)。
- 基于代理來實(shí)現(xiàn)(更加久遠(yuǎn)的一種實(shí)現(xiàn)方式,寫法笨重,不建議使用)。
四、代理模式
Spring AOP 是基于動態(tài)代理來實(shí)現(xiàn) AOP 的。
代理模式,也叫委托模式。
定義:
為其他對象提供一種代理,以控制對這個(gè)對象的訪問。它的作用就是通過提供一個(gè)代理類,讓我們在調(diào)用目標(biāo)方法的時(shí)候,不再是直接對目標(biāo)方法進(jìn)行調(diào)用,而是通過代理類間接調(diào)用。

代理模式可以在不修改被代理對象的基礎(chǔ)上,通過擴(kuò)展代理類,進(jìn)行一些功能的附加與增強(qiáng)。
根據(jù)代理的創(chuàng)建時(shí)期,代理模式分為靜態(tài)代理和動態(tài)代理。
- 靜態(tài)代理:由程序員創(chuàng)建代理類或特定工具自動生成源代碼再對其編譯,在程序運(yùn)行前代理類的 .class 文件就已經(jīng)存在了。
- 動態(tài)代理:在程序運(yùn)行時(shí),運(yùn)用反射機(jī)制動態(tài)創(chuàng)建而成。
到此這篇關(guān)于一文掌握Spring AOP的文章就介紹到這了,更多相關(guān)Spring AOP內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java替換int數(shù)組中重復(fù)數(shù)據(jù)的方法示例
這篇文章主要介紹了Java替換int數(shù)組中重復(fù)數(shù)據(jù)的方法,涉及java針對數(shù)組的遍歷、轉(zhuǎn)換、判斷等相關(guān)操作技巧,需要的朋友可以參考下2017-06-06
java實(shí)現(xiàn)將文件上傳到ftp服務(wù)器的方法
這篇文章主要介紹了java實(shí)現(xiàn)將文件上傳到ftp服務(wù)器的方法,結(jié)合實(shí)例形式分析了基于java實(shí)現(xiàn)的ftp文件傳輸類定義與使用方法,需要的朋友可以參考下2016-08-08
Springboot整合hibernate validator 全局異常處理步驟詳解
本文分步驟給大家介紹Springboot整合hibernate validator 全局異常處理,補(bǔ)呢文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-01-01
App登陸java后臺處理和用戶權(quán)限驗(yàn)證
這篇文章主要為大家詳細(xì)介紹了App登陸java后臺處理和用戶權(quán)限驗(yàn)證,感興趣的朋友可以參考一下2016-06-06
Idea中springboot項(xiàng)目的熱部署無法生效問題解決
本文主要介紹了Idea中springboot項(xiàng)目的熱部署無法生效問題解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-10-10
Java 入門圖形用戶界面設(shè)計(jì)之列表框JList
圖形界面(簡稱GUI)是指采用圖形方式顯示的計(jì)算機(jī)操作用戶界面。與早期計(jì)算機(jī)使用的命令行界面相比,圖形界面對于用戶來說在視覺上更易于接受,本篇精講Java語言中關(guān)于圖形用戶界面的列表框JList2022-02-02

