AOP基礎
學習完spring的事務管理之后,接下來我們進入到AOP的學習。 AOP也是spring框架的第二大核心,我們先來學習AOP的基礎。
在AOP基礎這個階段,我們首先介紹一下什么是AOP,再通過一個快速入門程序,讓大家快速體驗AOP程序的開發。最后再介紹AOP當中所涉及到的一些核心的概念。
AOP概述
什么是AOP?
-
AOP英文全稱:Aspect Oriented Programming(面向切面編程、面向方面編程),其實說白了,面向切面編程就是面向特定方法編程。
那什么又是面向方法編程呢,為什么又需要面向方法編程呢?來我們舉個例子做一個說明:
比如,我們這里有一個項目,項目中開發了很多的業務功能。
然而有一些業務功能執行效率比較低,執行耗時較長,我們需要針對于這些業務方法進行優化。 那首先第一步就需要定位出執行耗時比較長的業務方法,再針對于業務方法再來進行優化。
此時我們就需要統計當前這個項目當中每一個業務方法的執行耗時。那么統計每一個業務方法的執行耗時該怎么實現?
可能多數人首先想到的就是在每一個業務方法運行之前,記錄這個方法運行的開始時間。在這個方法運行完畢之后,再來記錄這個方法運行的結束時間。拿結束時間減去開始時間,不就是這個方法的執行耗時嗎?
以上分析的實現方式是可以解決需求問題的。但是對于一個項目來講,里面會包含很多的業務模塊,每個業務模塊又包含很多增刪改查的方法,如果我們要在每一個模塊下的業務方法中,添加記錄開始時間、結束時間、計算執行耗時的代碼,就會讓程序員的工作變得非常繁瑣。
而AOP面向方法編程,就可以做到在不改動這些原始方法的基礎上,針對特定的方法進行功能的增強。
AOP的作用:在程序運行期間在不修改源代碼的基礎上對已有方法進行增強(無侵入性:解耦)
我們要想完成統計各個業務方法執行耗時的需求,我們只需要定義一個模板方法,將記錄方法執行耗時這一部分公共的邏輯代碼,定義在模板方法當中,在這個方法開始運行之前,來記錄這個方法運行的開始時間,在方法結束運行的時候,再來記錄方法運行的結束時間,中間就來運行原始的業務方法。
而中間運行的原始業務方法,可能是其中的一個業務方法,比如:我們只想通過 部門管理的 list 方法的執行耗時,那就只有這一個方法是原始業務方法。 而如果,我們是先想統計所有部門管理的業務方法執行耗時,那此時,所有的部門管理的業務方法都是 原始業務方法。 那面向這樣的指定的一個或多個方法進行編程,我們就稱之為 面向切面編程。
那此時,當我們再調用部門管理的 list 業務方法時啊,并不會直接執行 list 方法的邏輯,而是會執行我們所定義的 模板方法 , 然后再模板方法中:
● 記錄方法運行開始時間
● 運行原始的業務方法(那此時原始的業務方法,就是 list 方法)
● 記錄方法運行結束時間,計算方法執行耗時
不論,我們運行的是那個業務方法,最后其實運行的就是我們定義的模板方法,而在模板方法中,就完成了原始方法執行耗時的統計操作 。(那這樣呢,我們就通過一個模板方法就完成了指定的一個或多個業務方法執行耗時的統計)
而大家會發現,這個流程,我們是不是似曾相識啊?
對了,就是和我們之前所學習的動態代理技術是非常類似的。我們所說的模板方法,其實就是代理對象中所定義的方法,那代理對象中的方法以及根據對應的業務需要, 完成了對應的業務功能,當運行原始業務方法時,就會運行代理對象中的方法,從而實現統計業務方法執行耗時的操作。
其實,AOP面向切面編程和OOP面向對象編程一樣,它們都僅僅是一種編程思想,而動態代理技術是這種思想最主流的實現方式。而Spring的AOP是Spring框架的高級技術,旨在管理bean對象的過程中底層使用動態代理機制,對特定的方法進行編程(功能增強)。
AOP的優勢:
1.減少重復代碼
2.提高開發效率
3.維護方便
AOP快速入門
在了解了什么是AOP后,我們下面通過一個快速入門程序,體驗下AOP的開發,并掌握Spring中AOP的開發步驟。
需求:統計各個業務層方法執行耗時。
實現步驟:
導入依賴:在pom.xml中導入AOP的依賴
編寫AOP程序:針對于特定方法根據業務需要進行編程
pom.xml
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
AOP程序:TimeAspect
@Component
@Aspect //當前類為切面類
@Slf4j
public class TimeAspect {@Around("execution(* com.itheima.service.*.*(..))") public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {//記錄方法執行開始時間long begin = System.currentTimeMillis();//執行原始方法Object result = pjp.proceed();//記錄方法執行結束時間long end = System.currentTimeMillis();//計算方法執行耗時log.info(pjp.getSignature()+"執行耗時: {}毫秒",end-begin);return result;}
}
重新啟動SpringBoot服務測試程序:
查詢3號部門信息
我們通過AOP入門程序完成了業務方法執行耗時的統計,那其實AOP的功能遠不止于此,常見的應用場景如下:
● 記錄系統的操作日志
● 權限控制
● 事務管理:我們前面所講解的Spring事務管理,底層其實也是通過AOP來實現的,只要添加@Transactional注解之后,AOP程序自動會在原始方法運行前先來開啟事務,在原始方法運行完畢之后提交或回滾事務
這些都是AOP應用的典型場景。
通過入門程序,我們也應該感受到了AOP面向切面編程的一些優勢:
● 代碼無侵入:沒有修改原始的業務方法,就已經對原始的業務方法進行了功能的增強或者是功能的改變
● 減少了重復代碼
● 提高開發效率
● 維護方便
AOP核心概念
通過SpringAOP的快速入門,感受了一下AOP面向切面編程的開發方式。下面我們再來學習AOP當中涉及到的一些核心概念。
連接點:JointPoint,可以被AOP控制的方法(暗含方法執行時的相關信息)
連接點指的是可以被aop控制的方法。例如:入門程序當中所有的業務方法都是可以被aop控制的方法。
在SpringAOP提供的JointPoint當中,封裝了連接點方法在執行時的相關信息。
通知:Advice,指哪些重復的邏輯,也就是共性功能(最終體現為一個方法)
在入門程序中是需要統計各個業務方法的執行耗時的,此時我們就需要在這些業務方法運行開始之前,先記錄這個方法運行的開始時間,在每一個業務方法運行結束的時候,再來記錄這個方法運行的結束時間。
但是在AOP面向切面編程當中,我們只需要將這部分重復的代碼邏輯抽取出來單獨定義。抽取出來的這一部分重復的邏輯,也就是共性的功能。
切入點:PointCut,匹配連接點的條件,通知僅會在切入點方法執行時被應用
在通知當中,我們所定義的共性功能到底要應用在哪些方法上?此時就涉及到了切入點pointcut概念。切入點指的是匹配連接點的條件。通知僅會在切入點方法運行時才會被應用。
在aop的開發當中,我們通常會通過一個切入點表達式來描述切入點。
假如:切入點表達式改為DeptServiceImpl.list(),此時就代表僅僅只有list這一個方法是切入點。只有list()方法在運行的時候才會應用通知。
切面:Aspect,描述通知與切入點的對應關系(通知+切入點)
當通知和切入點結合在一起,就形成了一個切面。通過切面就能夠描述當前aop程序需要針對于哪個原始方法,在什么時候執行什么樣的操作。
切面所在的類,我們一般稱為切面類(被@Aspect注解標識的類)
目標對象:Target,通知所應用的對象
目標對象指的就是通知所應用的對象,我們就稱之為目標對象。
AOP的核心概念我們介紹完畢之后,接下來我們再來分析一下我們所定義的通知是如何與目標對象結合在一起,對目標對象當中的方法進行功能增強的。
Spring的AOP底層是基于動態代理技術來實現的,也就是說在程序運行的時候,會自動的基于動態代理技術為目標對象生成一個對應的代理對象。在代理對象當中就會對目標對象當中的原始方法進行功能的增強。
AOP進階
AOP的基礎知識學習完之后,下面我們對AOP當中的各個細節進行詳細的學習。主要分為4個部分:
通知類型
通知順序
切入點表達式
連接點
我們先來學習第一部分通知類型。
通知類型
在入門程序當中,我們已經使用了一種功能最為強大的通知類型:Around環繞通知。
@Around("execution(* com.itheima.service.*.*(..))")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {//記錄方法執行開始時間long begin = System.currentTimeMillis();//執行原始方法Object result = pjp.proceed();//記錄方法執行結束時間long end = System.currentTimeMillis();//計算方法執行耗時log.info(pjp.getSignature()+"執行耗時: {}毫秒",end-begin);return result;
}
只要我們在通知方法上加上了@Around注解,就代表當前通知是一個環繞通知。
Spring中AOP的通知類型:
● @Around:環繞通知,此注解標注的通知方法在目標方法前、后都被執行
● @Before:前置通知,此注解標注的通知方法在目標方法前被執行
● @After:后置通知,此注解標注的通知方法在目標方法后被執行,無論是否有異常都會執行
● @AfterReturning:返回后通知,此注解標注的通知方法在目標方法后被執行,有異常不會執行
● @AfterThrowing:異常后通知,此注解標注的通知方法發生異常后執行
下面我們通過代碼演示,來加深對于不同通知類型的理解:
@Slf4j
@Component
@Aspect
public class MyAspect1 {//前置通知@Before("execution(* com.itheima.service.*.*(..))")public void before(JoinPoint joinPoint){log.info("before ...");}//環繞通知@Around("execution(* com.itheima.service.*.*(..))")public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {log.info("around before ...");//調用目標對象的原始方法執行Object result = proceedingJoinPoint.proceed();//原始方法如果執行時有異常,環繞通知中的后置代碼不會在執行了log.info("around after ...");return result;}//后置通知@After("execution(* com.itheima.service.*.*(..))")public void after(JoinPoint joinPoint){log.info("after ...");}//返回后通知(程序在正常執行的情況下,會執行的后置通知)@AfterReturning("execution(* com.itheima.service.*.*(..))")public void afterReturning(JoinPoint joinPoint){log.info("afterReturning ...");}//異常通知(程序在出現異常的情況下,執行的后置通知)@AfterThrowing("execution(* com.itheima.service.*.*(..))")public void afterThrowing(JoinPoint joinPoint){log.info("afterThrowing ...");}
}
重新啟動SpringBoot服務,進行測試:
1. 沒有異常情況下:
● 使用postman測試查詢所有部門數據
● 查看idea中控制臺日志輸出
程序沒有發生異常的情況下,@AfterThrowing標識的通知方法不會執行。
2. 出現異常情況下:
修改DeptServiceImpl業務實現類中的代碼: 添加異常
@Slf4j
@Service
public class DeptServiceImpl implements DeptService {@Autowiredprivate DeptMapper deptMapper;@Overridepublic List<Dept> list() {List<Dept> deptList = deptMapper.list();//模擬異常int num = 10/0;return deptList;}//省略其他代碼...
}
重新啟動SpringBoot服務,測試發生異常情況下通知的執行:
● 查看idea中控制臺日志輸出
程序發生異常的情況下:
@AfterReturning標識的通知方法不會執行,@AfterThrowing標識的通知方法執行了
@Around環繞通知中原始方法調用時有異常,通知中的環繞后的代碼邏輯也不會在執行了 (因為原始方法調用已經出異常了)
在使用通知時的注意事項:
● @Around環繞通知需要自己調用 ProceedingJoinPoint.proceed() 來讓原始方法執行,其他通知不需要考慮目標方法執行
● @Around環繞通知方法的返回值,必須指定為Object,來接收原始方法的返回值,否則原始方法執行完畢,是獲取不到返回值的。
五種常見的通知類型,我們已經測試完畢了,此時我們再來看一下剛才所編寫的代碼,有什么問題嗎?
//前置通知
@Before("execution(* com.itheima.service.*.*(..))")//環繞通知
@Around("execution(* com.itheima.service.*.*(..))")//后置通知
@After("execution(* com.itheima.service.*.*(..))")//返回后通知(程序在正常執行的情況下,會執行的后置通知)
@AfterReturning("execution(* com.itheima.service.*.*(..))")//異常通知(程序在出現異常的情況下,執行的后置通知)
@AfterThrowing("execution(* com.itheima.service.*.*(..))")
我們發現啊,每一個注解里面都指定了切入點表達式,而且這些切入點表達式都一模一樣。此時我們的代碼當中就存在了大量的重復性的切入點表達式,假如此時切入點表達式需要變動,就需要將所有的切入點表達式一個一個的來改動,就變得非常繁瑣了。
怎么來解決這個切入點表達式重復的問題? 答案就是:抽取
Spring提供了@PointCut注解,該注解的作用是將公共的切入點表達式抽取出來,需要用到時引用該切入點表達式即可。
@Slf4j
@Component
@Aspect
public class MyAspect1 {//切入點方法(公共的切入點表達式)@Pointcut("execution(* com.itheima.service.*.*(..))")private void pt(){}//前置通知(引用切入點)@Before("pt()")public void before(JoinPoint joinPoint){log.info("before ...");}//環繞通知@Around("pt()")public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {log.info("around before ...");//調用目標對象的原始方法執行Object result = proceedingJoinPoint.proceed();//原始方法在執行時:發生異常//后續代碼不在執行log.info("around after ...");return result;}//后置通知@After("pt()")public void after(JoinPoint joinPoint){log.info("after ...");}//返回后通知(程序在正常執行的情況下,會執行的后置通知)@AfterReturning("pt()")public void afterReturning(JoinPoint joinPoint){log.info("afterReturning ...");}//異常通知(程序在出現異常的情況下,執行的后置通知)@AfterThrowing("pt()")public void afterThrowing(JoinPoint joinPoint){log.info("afterThrowing ...");}
}
需要注意的是:當切入點方法使用private修飾時,僅能在當前切面類中引用該表達式, 當外部其他切面類中也要引用當前類中的切入點表達式,就需要把private改為public,而在引用的時候,具體的語法為:
全類名.方法名(),具體形式如下:
@Slf4j
@Component
@Aspect
public class MyAspect2 {//引用MyAspect1切面類中的切入點表達式@Before("com.itheima.aspect.MyAspect1.pt()")public void before(){log.info("MyAspect2 -> before ...");}
}
通知順序
講解完了Spring中AOP所支持的5種通知類型之后,接下來我們再來研究通知的執行順序。
當在項目開發當中,我們定義了多個切面類,而多個切面類中多個切入點都匹配到了同一個目標方法。此時當目標方法在運行的時候,這多個切面類當中的這些通知方法都會運行。
此時我們就有一個疑問,這多個通知方法到底哪個先運行,哪個后運行? 下面我們通過程序來驗證(這里呢,我們就定義兩種類型的通知進行測試,一種是前置通知@Before,一種是后置通知@After)
定義多個切面類:
@Slf4j
@Component
@Aspect
public class MyAspect2 {//前置通知@Before("execution(* com.itheima.service.*.*(..))")public void before(){log.info("MyAspect2 -> before ...");}//后置通知@After("execution(* com.itheima.service.*.*(..))")public void after(){log.info("MyAspect2 -> after ...");}
}
@Slf4j
@Component
@Aspect
public class MyAspect3 {//前置通知@Before("execution(* com.itheima.service.*.*(..))")public void before(){log.info("MyAspect3 -> before ...");}//后置通知@After("execution(* com.itheima.service.*.*(..))")public void after(){log.info("MyAspect3 -> after ...");}
}
@Slf4j
@Component
@Aspect
public class MyAspect4 {//前置通知@Before("execution(* com.itheima.service.*.*(..))")public void before(){log.info("MyAspect4 -> before ...");}//后置通知@After("execution(* com.itheima.service.*.*(..))")public void after(){log.info("MyAspect4 -> after ...");}
}
重新啟動SpringBoot服務,測試通知的執行順序:
備注:
? ? 1.把DeptServiceImpl實現類中模擬異常的代碼刪除或注釋掉。
? ? 2.注釋掉其他切面類(把@Aspect注釋即可),僅保留MyAspect2、MyAspect3、MyAspect4 ,這樣就可以清晰看到執行的結果,而不被其他切面類干擾。
● 使用postman測試查詢所有部門數據
● 查看idea中控制臺日志輸出
通過以上程序運行可以看出在不同切面類中,默認按照切面類的類名字母排序:
● 目標方法前的通知方法:字母排名靠前的先執行
● 目標方法后的通知方法:字母排名靠前的后執行
如果我們想控制通知的執行順序有兩種方式:
? ? 1.修改切面類的類名(這種方式非常繁瑣、而且不便管理)
? ? 2.使用Spring提供的@Order注解
使用@Order注解,控制通知的執行順序:
@Slf4j
@Component
@Aspect
@Order(2) //切面類的執行順序(前置通知:數字越小先執行; 后置通知:數字越小越后執行)
public class MyAspect2 {//前置通知@Before("execution(* com.itheima.service.*.*(..))")public void before(){log.info("MyAspect2 -> before ...");}//后置通知 @After("execution(* com.itheima.service.*.*(..))")public void after(){log.info("MyAspect2 -> after ...");}
}
@Slf4j
@Component
@Aspect
@Order(3) //切面類的執行順序(前置通知:數字越小先執行; 后置通知:數字越小越后執行)
public class MyAspect3 {//前置通知@Before("execution(* com.itheima.service.*.*(..))")public void before(){log.info("MyAspect3 -> before ...");}//后置通知@After("execution(* com.itheima.service.*.*(..))")public void after(){log.info("MyAspect3 -> after ...");}
}
@Slf4j
@Component
@Aspect
@Order(1) //切面類的執行順序(前置通知:數字越小先執行; 后置通知:數字越小越后執行)
public class MyAspect4 {//前置通知@Before("execution(* com.itheima.service.*.*(..))")public void before(){log.info("MyAspect4 -> before ...");}//后置通知@After("execution(* com.itheima.service.*.*(..))")public void after(){log.info("MyAspect4 -> after ...");}
}
重新啟動SpringBoot服務,測試通知執行順序:
通知的執行順序大家主要知道兩點即可:
不同的切面類當中,默認情況下通知的執行順序是與切面類的類名字母排序是有關系的
可以在切面類上面加上@Order注解,來控制不同的切面類通知的執行順序
切入點表達式
從AOP的入門程序到現在,我們一直都在使用切入點表達式來描述切入點。下面我們就來詳細的介紹一下切入點表達式的具體寫法。
切入點表達式:
描述切入點方法的一種表達式
作用:主要用來決定項目中的哪些方法需要加入通知
常見形式:
execution(……):根據方法的簽名來匹配
@annotation(……) :根據注解匹配
首先我們先學習第一種最為常見的execution切入點表達式。
execution
execution主要根據方法的返回值、包名、類名、方法名、方法參數等信息來匹配,語法為:
execution(訪問修飾符? 返回值 包名.類名.?方法名(方法參數) throws 異常?)
其中帶?
的表示可以省略的部分
? ? ● 訪問修飾符:可省略(比如: public、protected)
? ? ● 包名.類名: 可省略
? ? ● throws 異常:可省略(注意是方法上聲明拋出的異常,不是實際拋出的異常)
示例:
@Before("execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
可以使用通配符描述切入點
? ● *
:單個獨立的任意符號,可以通配任意返回值、包名、類名、方法名、任意類型的一個參數,也可以通配包、類、方法名的一部分
? ● ..
:多個連續的任意符號,可以通配任意層級的包,或任意類型、任意個數的參數
切入點表達式的語法規則:
? ? 1.方法的訪問修飾符可以省略
? ? 2.返回值可以使用*
號代替(任意返回值類型)
? ? 3.包名可以使用*
號代替,代表任意包(一層包使用一個*
)
? ? 4.使用..
配置包名,標識此包以及此包下的所有子包
? ? 5.類名可以使用*
號代替,標識任意類
? ? 6.方法名可以使用*
號代替,表示任意方法
? ? 7.可以使用 *
配置參數,一個任意類型的參數
? ? 8.可以使用..
配置參數,任意個任意類型的參數
切入點表達式示例
● 省略方法的修飾符號
execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))
●?使用*
代替返回值類型
execution(* com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))
● 使用*
代替包名(一層包使用一個*
)
execution(* com.itheima.*.*.DeptServiceImpl.delete(java.lang.Integer))
●?使用..
省略包名
execution(* com..DeptServiceImpl.delete(java.lang.Integer))
●?使用*
代替類名
execution(* com..*.delete(java.lang.Integer))
●?使用*
代替方法名
execution(* com..*.*(java.lang.Integer))
●?使用 *
代替參數
execution(* com.itheima.service.impl.DeptServiceImpl.delete(*))
●?使用..
省略參數
execution(* com..*.*(..))
注意事項:
●?根據業務需要,可以使用 且(&&)、或(||)、非(!) 來組合比較復雜的切入點表達式。
execution(* com.itheima.service.DeptService.list(..)) || execution(* com.itheima.service.DeptService.delete(..))
切入點表達式的書寫建議:
? ??●?所有業務方法名在命名時盡量規范,方便切入點表達式快速匹配。如:查詢類方法都是 find 開頭,更新類方法都是update開頭
//業務類
@Service
public class DeptServiceImpl implements DeptService {public List<Dept> findAllDept() {//省略代碼...}public Dept findDeptById(Integer id) {//省略代碼...}public void updateDeptById(Integer id) {//省略代碼...}public void updateDeptByMoreCondition(Dept dept) {//省略代碼...}//其他代碼...
}
//匹配DeptServiceImpl類中以find開頭的方法
execution(* com.itheima.service.impl.DeptServiceImpl.find*(..))
? ??●?描述切入點方法通常基于接口描述,而不是直接描述實現類,增強拓展性
execution(* com.itheima.service.DeptService.*(..))
? ??●?在滿足業務需要的前提下,盡量縮小切入點的匹配范圍。如:包名匹配盡量不使用 ..,使用 * 匹配單個包
execution(* com.itheima.*.*.DeptServiceImpl.find*(..))
@annotation
已經學習了execution切入點表達式的語法。那么如果我們要匹配多個無規則的方法,比如:list()和 delete()這兩個方法。這個時候我們基于execution這種切入點表達式來描述就不是很方便了。而在之前我們是將兩個切入點表達式組合在了一起完成的需求,這個是比較繁瑣的。
我們可以借助于另一種切入點表達式annotation來描述這一類的切入點,從而來簡化切入點表達式的書寫。
實現步驟:
? ? 1.編寫自定義注解
? ? 2.在業務類要做為連接點的方法上添加自定義注解
自定義注解:MyLog
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
}
業務類:DeptServiceImpl
@Slf4j
@Service
public class DeptServiceImpl implements DeptService {@Autowiredprivate DeptMapper deptMapper;@Override@MyLog //自定義注解(表示:當前方法屬于目標方法)public List<Dept> list() {List<Dept> deptList = deptMapper.list();//模擬異常//int num = 10/0;return deptList;}@Override@MyLog //自定義注解(表示:當前方法屬于目標方法)public void delete(Integer id) {//1. 刪除部門deptMapper.delete(id);}@Overridepublic void save(Dept dept) {dept.setCreateTime(LocalDateTime.now());dept.setUpdateTime(LocalDateTime.now());deptMapper.save(dept);}@Overridepublic Dept getById(Integer id) {return deptMapper.getById(id);}@Overridepublic void update(Dept dept) {dept.setUpdateTime(LocalDateTime.now());deptMapper.update(dept);}
}
切面類
@Slf4j
@Component
@Aspect
public class MyAspect6 {//針對list方法、delete方法進行前置通知和后置通知//前置通知@Before("@annotation(com.itheima.anno.MyLog)")public void before(){log.info("MyAspect6 -> before ...");}//后置通知@After("@annotation(com.itheima.anno.MyLog)")public void after(){log.info("MyAspect6 -> after ...");}
}
重啟SpringBoot服務,測試查詢所有部門數據,查看控制臺日志:
到此我們兩種常見的切入點表達式我已經介紹完了。
? ??●?execution切入點表達式
? ? ? ??○?根據我們所指定的方法的描述信息來匹配切入點方法,這種方式也是最為常用的一種方式
? ? ? ??○?如果我們要匹配的切入點方法的方法名不規則,或者有一些比較特殊的需求,通過execution切入點表達式描述比較繁瑣
? ??●?annotation 切入點表達式
? ? ? ??○?基于注解的方式來匹配切入點方法。這種方式雖然多一步操作,我們需要自定義一個注解,但是相對來比較靈活。我們需要匹配哪個方法,就在方法上加上對應的注解就可以了
連接點
講解完了切入點表達式之后,接下來我們再來講解最后一個部分連接點。我們前面在講解AOP核心概念的時候,我們提到過什么是連接點,連接點可以簡單理解為可以被AOP控制的方法。
我們目標對象當中所有的方法是不是都是可以被AOP控制的方法。而在SpringAOP當中,連接點又特指方法的執行。
在Spring中用JoinPoint抽象了連接點,用它可以獲得方法執行時的相關信息,如目標類名、方法名、方法參數等。
? ? ● 對于@Around通知,獲取連接點信息只能使用ProceedingJoinPoint類型
? ? ● 對于其他四種通知,獲取連接點信息只能使用JoinPoint,它是ProceedingJoinPoint的父類型
示例代碼:
@Slf4j
@Component
@Aspect
public class MyAspect7 {@Pointcut("@annotation(com.itheima.anno.MyLog)")private void pt(){}//前置通知@Before("pt()")public void before(JoinPoint joinPoint){log.info(joinPoint.getSignature().getName() + " MyAspect7 -> before ...");}//后置通知@Before("pt()")public void after(JoinPoint joinPoint){log.info(joinPoint.getSignature().getName() + " MyAspect7 -> after ...");}//環繞通知@Around("pt()")public Object around(ProceedingJoinPoint pjp) throws Throwable {//獲取目標類名String name = pjp.getTarget().getClass().getName();log.info("目標類名:{}",name);//目標方法名String methodName = pjp.getSignature().getName();log.info("目標方法名:{}",methodName);//獲取方法執行時需要的參數Object[] args = pjp.getArgs();log.info("目標方法參數:{}", Arrays.toString(args));//執行原始方法Object returnValue = pjp.proceed();return returnValue;}
}
重新啟動SpringBoot服務,執行查詢部門數據的功能: