Springboot AOP開發(fā)教程
Springboot AOP開發(fā)
簡(jiǎn)介
AOP(Aspect Oriented Programming)意為:面向切面編程,通過預(yù)編譯方式和運(yùn)行期動(dòng)態(tài)代理實(shí)現(xiàn)程序功能的統(tǒng)一維護(hù)的一種技術(shù)。AOP是OOP的延續(xù),是軟件開發(fā)中的一個(gè)熱點(diǎn),也是Spring框架中的一個(gè)重要內(nèi)容,是函數(shù)式編程的一種衍生范型。利用AOP可以對(duì)業(yè)務(wù)邏輯的各個(gè)部分進(jìn)行隔離,從而使得業(yè)務(wù)邏輯各部分之間的耦合度降低,提高程序的可重用性,同時(shí)提高了開發(fā)的效率。
一 AOP概述
AOP,即面向切面編程,簡(jiǎn)言之,面向方法編程。
針對(duì)方法,在方法的執(zhí)行前或執(zhí)行后使用,用于增強(qiáng)方法,或拓展。
二 AOP開發(fā)
1.引入 spring-boot-starter-aop
在SpringBoot項(xiàng)目的pom文件中,引入 spring-boot-starter-aop依賴。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
2.示例:計(jì)算方法執(zhí)行時(shí)間
1.創(chuàng)建實(shí)體類,通過注解來(lái)申明該類的類型,并將該類交給Spring的IOC容器來(lái)管理。
通過注解 @Aspect
申明這是一個(gè)AOP類
通過 @Component
將其交給IOC容器管理
@Aspect @Component public class TimeAspect { //code }
2.創(chuàng)建方法并且實(shí)現(xiàn)
@Aspect @Component @Slf4j public class TimeAspect { // 針對(duì) com.shawn.springboot03.service 包下所有的方法進(jìn)行編程, // * com.shawn.springboot03.service.*.*(..)) 為切入點(diǎn)表達(dá)式 @Around("execution(* com.shawn.springboot03.service.*.*(..))") public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { long startTime=System.currentTimeMillis(); // 切面對(duì)象,執(zhí)行具體的業(yè)務(wù)方法 Object object = proceedingJoinPoint.proceed(); long endTime=System.currentTimeMillis(); log.info("方法耗時(shí)為{}ms",endTime-startTime); return object; } }
通過以上方法,當(dāng)用戶調(diào)用 service 層接口的任一方法時(shí)都會(huì)計(jì)算方法的運(yùn)行時(shí)間。
3.AOP編程的優(yōu)點(diǎn):
- 代碼無(wú)侵入:無(wú)需修改原始方法
- 減少代碼重復(fù),提高開發(fā)效率:只需編寫一次
- 維護(hù)方便:根據(jù)業(yè)務(wù)需求,調(diào)整切入點(diǎn)表達(dá)式即可
三 AOP詳解
1.AOP核心概念
1.連接點(diǎn)(JoinPoint):連接點(diǎn)指的是可以被AOP控制的方法,以及方法執(zhí)行時(shí)的相關(guān)信息。
2.通知(Advice):Advice指的是被抽取出來(lái)的共性功能,即重復(fù)的那部分邏輯。
3.切入點(diǎn)(PointCut):匹配連接點(diǎn)的條件,通知僅會(huì)在切入點(diǎn)方法執(zhí)行時(shí)被應(yīng)用
4.切面(Aspcet):描述通知與切入點(diǎn)的關(guān)系
5.目標(biāo)對(duì)象(Target):通知所應(yīng)用的對(duì)象
2.AOP通知類型
- @Around:環(huán)繞通知,此注解標(biāo)注的方法在目標(biāo)方法前后都會(huì)執(zhí)行
- @Before:前置通知,此注解標(biāo)注的方法僅在方法執(zhí)行前被執(zhí)行
- @After:后置通知,此注解在方法執(zhí)行完成后執(zhí)行,不論是否拋出異常
- @AfterReturning:返回后通知,此注解標(biāo)注的方法在目標(biāo)方法后被執(zhí)行,有異常不通知
- @AfterThrowing:異常后通知,此注解的通知方法發(fā)生異常后執(zhí)行
3.各通知類型演示
創(chuàng)建一個(gè) TestAspect 類,用于演示各種通知類型
@Around 通知類型
介紹:在方法前后均執(zhí)行
切入點(diǎn)表達(dá)式格式: 返回值 包名.方法名(形參)
其中 *
代表全部,..
代表任意多的參數(shù)
切入點(diǎn)表達(dá)式示例說(shuō)明:
* com.shawn.test.server.*(..)
表示 任意返回值的 com.shawn.test.server
包下任意返回值,任意多參數(shù)的全部方法。
@Aspect @Component @Slf4j public class TestAspect { @Around("execution(* com.shawn.springboot03.service.impl.DeptServiceImpl.*(..))") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { //方法執(zhí)行前的業(yè)務(wù)邏輯 //code.. //執(zhí)行原始方法(即連接點(diǎn)),并且接收原始方法的返回值 Object proceed = proceedingJoinPoint.proceed(); //方法執(zhí)行后的業(yè)務(wù)邏輯 //code.. //返回原始方法的返回值 return proceed; } }
ProceedingJoinPoint
是一個(gè)接口類,指代程序執(zhí)行過程中的一個(gè)特定點(diǎn),其中包含了原始方法的全部?jī)?nèi)容,包括方法名,參數(shù)值等。
在使用 @Around
通知類型時(shí),需要將該對(duì)象作為參數(shù)
傳遞進(jìn)來(lái),用于后續(xù)執(zhí)行原始方法或獲取原始方法的其他信息。
ProceedingJoinPoint
僅能作用于 @Around
類型的通知上。
2.@Before 通知類型
介紹:此注解標(biāo)注的方法僅在原始方法執(zhí)行前執(zhí)行
@Aspect @Component @Slf4j public class TestAspect { @Before("execution(* com.shawn.springboot03.service.*.*(..))") public void before(){ // 方法執(zhí)行前的業(yè)務(wù)邏輯 //code.. } }
3.@After 通知類型
介紹:此注解標(biāo)注的方法,僅在原始方法執(zhí)行后執(zhí)行,且不論原始方法是否執(zhí)行,該通知都會(huì)執(zhí)行
@Aspect @Component @Slf4j public class TestAspect { @After("execution(* com.shawn.springboot03.service.*.*(..))") public void after(){ // 方法執(zhí)行后的業(yè)務(wù)邏輯 //code.. } }
4.@AfterReturning 通知類型
介紹:此注解標(biāo)注的方法,僅在原始方法執(zhí)行完成后通知,即當(dāng)原始方法執(zhí)行發(fā)生異常時(shí),@AfterReturning
通知不會(huì)被執(zhí)行。
@Aspect @Component @Slf4j public class TestAspect { @AfterReturning("execution(* com.shawn.springboot03.service.*.*(..))") public void afterReturning(){ //方法執(zhí)行完成的業(yè)務(wù)邏輯 //code.. } }
5.@AfterThrowing通知類型
介紹:次注解標(biāo)注的方法,僅在原始方法執(zhí)行過程中發(fā)生了異常,才會(huì)執(zhí)行。
@Aspect @Component @Slf4j public class TestAspect { /** * 聲明一個(gè)空的方法體來(lái)定義切入點(diǎn)表達(dá)式 * 使用 @Pointcut 注解來(lái)定義切入點(diǎn)表達(dá)式 */ @Pointcut("execution(* com.shawn.springboot03.service.impl.DeptServiceImpl.*(..))") private void point(){}; @After("point()") public void after(){ // 方法執(zhí)行后的業(yè)務(wù)邏輯 //code.. } }
4.定義切入點(diǎn)
以上示例中的切入點(diǎn)表達(dá)式均相似,可以利用封裝的思想,將切入點(diǎn)表達(dá)式全部抽取出來(lái),在需要的時(shí)候直接使用即可。
想要實(shí)現(xiàn)以上需求,則需要自定義切入點(diǎn)表達(dá)式
通過 @Pointcut
注解,來(lái)定義切入點(diǎn)表達(dá)式,然后在需要編寫切入點(diǎn)表達(dá)式的地方調(diào)用即可。
@Aspect @Component public class TestAspect { @Pointcut("@annotation(com.shawn.springboot03.annotation.OperationLog)") private void point(){}; @AfterThrowing("point()") public void afterThrowing(){ //方法執(zhí)行時(shí)拋出異常的業(yè)務(wù)邏輯 //code.. } }
注意:如果自定義的切入點(diǎn)訪問修飾符為 public
,則該表達(dá)式還可以在其他切面類中被引用。具體使用可根據(jù)實(shí)際業(yè)務(wù)情況決定。
定義切入點(diǎn)總共有兩種辦法,一種是通過方法名來(lái)定義切入點(diǎn),還可以通過注解來(lái)定義切入點(diǎn)。
示例:以下示例使用自定義注解作為切入點(diǎn),使用了以下自定義注解的方法會(huì)執(zhí)行通知。
@Aspect @Component @Slf4j @Order(3) public class TestAspect { @Pointcut("execution(* com.shawn.springboot03.service.impl.DeptServiceImpl.*(..))") private void point(){}; @After("point()") public void after(){ // 方法執(zhí)行后的業(yè)務(wù)邏輯 //code.. } }
5.通知順序
當(dāng)有多個(gè)切入點(diǎn)都匹配到了目標(biāo)方法,目標(biāo)方法運(yùn)行時(shí),多個(gè)通知都會(huì)被執(zhí)行。
某些情況下則需要控制通知的順序,可以在切面類上使用 @Order
注解來(lái)控制順序
@Aspect @Component @Slf4j @Order(3) public class TestAspect { @Pointcut("execution(* com.shawn.springboot03.service.impl.DeptServiceImpl.*(..))") private void point(){}; @After("point()") public void after(){ // 方法執(zhí)行后的業(yè)務(wù)邏輯 //code.. } }
注意:
@Order(number)
注解中,數(shù)字越小的越先運(yùn)行,越大的越后運(yùn)行。默認(rèn)情況下,按照切面類的類名排序順序執(zhí)行。
四 切入點(diǎn)表達(dá)式
1.execution
execution 主要根據(jù)方法的返回值,包名類名,方法名,方法參數(shù)等信息來(lái)匹配,語(yǔ)法為:
execution( 訪問修飾符? 返回值 包名.類名.?方法名(方法參數(shù)) throw 異常? )
其中帶 ? 的表示可以省略的部分
- 訪問修飾符:可省略(比如:public,protected)
- 包名.類名:可省略
- throws 異常:可省略(注意是方法上聲明拋出的異常,不是實(shí)際拋出的異常)
以下是一個(gè)完整示例:
@Before ("execution(public void com.itheima.service.impl.DeptserviceImpl.delete (java.lang.Integer)) throws Exception") private void point(){};
省略后的示例:
@Before ("execution(void delete (java.lang.Integer))") private void point(){};
此時(shí),所有 void delete (java.lang.Integer)
方法都將被匹配到。
注意: * 用于描述匹配單個(gè), … 可以用來(lái)匹配多個(gè)連續(xù),?表示可省略
2.@annotation
@annotation
切入點(diǎn)表達(dá)式,用于匹配標(biāo)識(shí)有特定注解的方法。
示例:
@Pointcut("@annotation(com.shawn.annotation.OperationLog)") private void point(){};
五 連接點(diǎn)
在Spring中使用JoinPoint
抽象了連接點(diǎn),使用它可以獲取方法執(zhí)行時(shí)的相關(guān)信息,入目標(biāo)類名,方法名,方法參數(shù)等。
1.對(duì)于 @Around
通知,獲取連接點(diǎn)信息只能使用 ProceedingJoinPoint
@Around("point()") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { //獲取目標(biāo)類名 String name = proceedingJoinPoint.getTarget().getClass().getName(); log.info("獲取目標(biāo)類名:"+name); //獲取方法執(zhí)行參數(shù) Object[] args = proceedingJoinPoint.getArgs(); log.info("獲取方法執(zhí)行參數(shù):"+JSON.toJSONString(args)); //獲取目標(biāo)方法名 String methodName = proceedingJoinPoint.getSignature().getName(); log.info("獲取目標(biāo)方法名:"+methodName); //執(zhí)行原始方法(即連接點(diǎn)),并且接收原始方法的返回值 Object proceed = proceedingJoinPoint.proceed(); //獲取方法的返回值 log.info("獲取方法的返回值:"+JSON.toJSONString(proceed)); //返回原始方法的返回值 return proceed; }
proceedingJoinPoint.proceed()得到目標(biāo)方法的結(jié)果再返回,此處可以對(duì)目標(biāo)方法執(zhí)行的結(jié)果進(jìn)行篡改。
2.對(duì)于其他四種通知,獲取連接點(diǎn)信息只能使用 JoinPoint
, 它是 ProceedingJoinPoint
的父類。
@Before("point()") public void before(JoinPoint joinPoint){ // 獲取目標(biāo)方法的類名 String name = joinPoint.getTarget().getClass().getName(); log.info("獲取目標(biāo)方法的類名:"+name); String methodName = joinPoint.getSignature().getName(); log.info("獲取目標(biāo)方法的方法名:"+methodName); //獲取目標(biāo)方法的運(yùn)行參數(shù) Object[] args = joinPoint.getArgs(); log.info("獲取目標(biāo)方法的參數(shù):"+JSON.toJSONString(args)); }
3.除@Around 通知外,還可以獲取到方法執(zhí)行結(jié)果的通知類型為 @AfterReturning,@AfterReturning 在方法執(zhí)行完成并且無(wú)異常時(shí)通知。
在定義@AfterReturning通知類型時(shí),使用 pointcut 屬性定義切入點(diǎn),returning屬性定義返回值對(duì)象,然后在方法參數(shù)中傳入即可。
@AfterReturning(pointcut = "point()",returning = "object") public void afterReturning(JoinPoint joinPoint, Object object){ // 獲取目標(biāo)方法的類名 String name = joinPoint.getTarget().getClass().getName(); log.info("獲取目標(biāo)方法的類名:"+name); String methodName = joinPoint.getSignature().getName(); log.info("獲取目標(biāo)方法的方法名:"+methodName); //獲取目標(biāo)方法的運(yùn)行參數(shù) Object[] args = joinPoint.getArgs(); log.info("獲取目標(biāo)方法的參數(shù):"+JSON.toJSONString(args)); //獲取方法返回值 log.info("獲取方法返回值:"+JSON.toJSONString(object)); }
到此這篇關(guān)于Springboot AOP開發(fā)的文章就介紹到這了,更多相關(guān)Springboot AOP開發(fā)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
深入理解 Java 中的 Switch 語(yǔ)句示例詳解
在Java編程中,switch語(yǔ)句通過表達(dá)式值來(lái)執(zhí)行不同代碼塊,本文介紹switch語(yǔ)法、案例、注意事項(xiàng),以及與if語(yǔ)句的對(duì)比,包括基本語(yǔ)法、關(guān)鍵字、表達(dá)式、case常量、break和default的使用,以及如何根據(jù)輸入的字符輸出星期、大小寫轉(zhuǎn)換、成績(jī)判斷和季節(jié)判斷等實(shí)際應(yīng)用場(chǎng)景2024-10-10java中不定長(zhǎng)參數(shù)的實(shí)例用法
在本篇文章里小編給大家分享的是關(guān)于java中不定長(zhǎng)參數(shù)的使用方法以及相關(guān)代碼內(nèi)容,有興趣的朋友們可以學(xué)習(xí)參考下。2020-02-02IntelliJ?IDEA?2020.2?全家桶及以下版本激活工具大全【喜訊】
這篇文章主要介紹了IntelliJ?IDEA?2020.2?全家桶及以下版本激活工具大全【喜訊】,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09Spring實(shí)現(xiàn)內(nèi)置監(jiān)聽器
這篇文章主要介紹了Spring 實(shí)現(xiàn)自定義監(jiān)聽器案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來(lái)看看吧,希望能給你帶來(lái)幫助2021-07-07Java?Excel?Poi字體顏色自定義設(shè)置代碼
最近項(xiàng)目使用POI按模板導(dǎo)出Excel,需要設(shè)置單元格的字體為紅色,下面這篇文章主要給大家介紹了關(guān)于Java?Excel?Poi字體顏色自定義設(shè)置的相關(guān)資料,需要的朋友可以參考下2024-01-01深入理解Spring事務(wù)及傳播機(jī)制之原理解析與實(shí)際應(yīng)用
Spring事務(wù)管理機(jī)制提供了多種傳播行為,可以控制事務(wù)的范圍和隔離級(jí)別,保證數(shù)據(jù)一致性和完整性。在實(shí)際應(yīng)用中,需要根據(jù)具體業(yè)務(wù)場(chǎng)景選擇合適的傳播行為實(shí)現(xiàn)事務(wù)控制2023-04-04springboot啟動(dòng)時(shí)候報(bào)錯(cuò)mongodb問題
這篇文章主要介紹了springboot啟動(dòng)時(shí)候報(bào)錯(cuò)mongodb問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05java使double保留兩位小數(shù)的多方法 java保留兩位小數(shù)
這篇文章主要介紹了java使double類型保留兩位小數(shù)的方法,大家參考使用吧2014-01-01javax.validation包里@NotNull等注解的使用方式
這篇文章主要介紹了javax.validation包里@NotNull等注解的使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01