AOP(黑馬學習筆記)

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服務,執行查詢部門數據的功能:

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/713155.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/713155.shtml
英文地址,請注明出處:http://en.pswp.cn/news/713155.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

JAVASE初認識

1.初認識其結構 1.源文件&#xff08;擴展名為*.java)&#xff1a;源文件帶有類的定義。類用來表示程序的一個組件&#xff0c;小程序或許只會有一個類。類的內容必須包含在花括號里面。 2.類&#xff1a;類中帶有一個或多個方法。方法必須在類的內部聲明。 3.方法&#xff1…

vue3創建h5 項目使用rem做響應式的配置

第一步 安裝依賴&#xff1a; npm install amfe-flexible -S npm install postcss-px2rem -S第二步 main.ts文件中導入 import "amfe-flexible/index.js";第三步 進行配置&#xff1a; vue3 項目中創建 postcss.cinfig.js文件&#xff0c;這里是基于設計稿是750px…

gRPC知識歸檔

文章目錄 gRPC知識歸檔gRPC原理什么是gRPCgRPC的特性gRPC支持語言gRPC使用場景gRPC設計的動機和原則 數據封裝和數據傳輸問題網絡傳輸中的內容封裝和數據體積問題JSONProtobuf&#xff08;微服務之間的服務器調用&#xff0c;一般采用二進制序列化&#xff0c;比如protobuf&…

精讀《React Hooks 最佳實踐》

簡介 React 16.8 于 2019.2 正式發布&#xff0c;這是一個能提升代碼質量和開發效率的特性&#xff0c;筆者就拋磚引玉先列出一些實踐點&#xff0c;希望得到大家進一步討論。 然而需要理解的是&#xff0c;沒有一個完美的最佳實踐規范&#xff0c;對一個高效團隊來說&#x…

【airtest】自動化入門教程(二)airtest操作

目錄 一、touch 二、wait 三、swipe 四、exists 五、text 六、keyevent 七、snapshot 八、sleep 九、斷言 9.1 assert_exists 9.2 assert_not_exists 9.3 assert_equal 9.4 assert_not_equal 前言&#xff1a;本文主要針對aritest部分的基礎操作,aritest是一個跨平…

網絡編程第二天

1.基于TCP的通信(面向連接的通信) 服務器代碼實現&#xff1a; #include <myhead.h> #define IP "192.168.126.91" #define PORT 9999 int main(int argc, const char *argv[]) {//1、創建套接字int sfd-1;if((sfdsocket(AF_INET,SOCK_STREAM,0))-1){perror(…

LeetCode 76 最小覆蓋字串

LeetCode 76 最小覆蓋字串 在本篇博客中&#xff0c;我們將探討LeetCode上的一道算法題目——“最小覆蓋子串”。這道題的主要目標是找到字符串s中包含字符串t中所有字符的最小子串。 問題描述 給定字符串s和t&#xff0c;要求在字符串s中找到一個最小的子串&#xff0c;使得…

5.36 BCC工具之ucalls.py解讀

一,工具簡介 ucalls工具總結了包括Java、Perl、PHP、Python、Ruby、Tcl和Linux系統調用在內的各種高級語言中的方法調用。它顯示最常調用方法的統計信息,以及這些方法的延遲(持續時間)。 通過系統調用支持,ucalls可以提供關于進程與系統交互的基本信息,包括系統調用計數…

ES系列之Logstash實戰入門

概述 作為ELK技術棧一員&#xff0c;Logstash用于將數據采集到ES&#xff0c;通過簡單配置就能把各種外部數據采集到索引中進行保存&#xff0c;可提高數據采集的效率。 原理 數據源提供的數據進入Logstash的管道后需要經過3個階段&#xff1a; input&#xff1a;負責抽取數…

C#單向鏈表實現:在當前節點后插入新數據的方法Insert()

目錄 一、涉及到的知識點 1.插入算法 2.示例中current 和 _current 的作用 3.current 和 _current 能否合并為一個變量 4.單向鏈表節點類的三個屬性 &#xff08;1&#xff09;Next屬性&#xff1a; &#xff08;2&#xff09; Value屬性&#xff1a; &#xff08;3&am…

【ArcPy】批量讀取文件夾excel中XY并轉為點shp

示例展示 代碼 只讀取excel中含有XY字段的文件&#xff0c;并將矢量命名為excel文件名稱。 import os import pandas as pd import arcpy folder_path r"C:\Users\admin\Desktop\excelfile" extension"xlsx" files [file for file in os.listdir(folder…

SpringCloud gateway限流無效,redis版本低的問題

在使用springCloud gateway的限流功能的時候&#xff0c;配置RedisRateLimiter限流無效&#xff0c;后來發現是Redis版本過低導致的問題&#xff0c;實測 Redis版本為3.0.504時限流無效&#xff0c;改用7.0.x版本的Redis后限流生效。查了資料發現很多人都遇見過這個問題&#x…

RedisTemplate 序列化成功,反序列化失敗List, Set, Map失敗

RedisTemplate 序列化成功&#xff0c;反序列化失敗List, Set, Map失敗 異常信息RedisTemplate配置異常原因錯誤代碼示例解決方法 序列化成功&#xff0c;反序列化失敗 異常信息 Caused by: com.fasterxml.jackson.databind.exc.InvalidTypeIdException: Could not resolve ty…

小程序事件處理

事件處理 一個應用僅僅只有界面展示是不夠的&#xff0c;還需要和用戶做交互&#xff0c;例如&#xff1a;響應用戶的點擊、獲取用戶輸入的值等等&#xff0c;在小程序里邊&#xff0c;我們就通過編寫 JS 腳本文件來處理用戶的操作 1. 事件綁定和事件對象 小程序中綁定事件與…

React之組件定義和事件處理

一、組件的分類 在react中&#xff0c;組件分為函數組件和class組件&#xff0c;也就是無狀態組件和有狀態組件。 * 更過時候我們應該區別使用無狀態組件&#xff0c;因為如果有狀態組件會觸發生命周期所對應的一些函數 * 一旦觸發他生命周期的函數&#xff0c;它就會影響當前項…

如何設置從小程序跳轉到其它小程序

?有的商家有多個小程序&#xff0c;希望能夠通過一個小程序鏈接到所有其它小程序&#xff0c;用戶可以通過點擊跳轉鏈接實現從一個小程序跳轉到另一個小程序。要怎么才能實現這樣的跳轉呢。下面具體介紹。 1. 設置跳轉。在小程序管理員后臺->分類管理&#xff0c;添加一個…

ssm個人學習01

Spring配置文件: spring環境的搭建: 1:導入對應的spring坐標 也就是依賴 2:編寫controller, service, dao相關的代碼 3:創建配置文件(在resource下面配置文件) 例如:applicationContext.xml <bean id "" class ""> <property name "&…

Node.js 中 fs 模塊文件操作的應用教程

Node.js 是一個基于 Chrome V8 引擎的 JavaScript 運行環境&#xff0c;它可以讓 JavaScript 代碼在服務器端運行。在 Node.js 中&#xff0c;fs 模塊是用來處理文件系統操作的模塊。通過 fs 模塊&#xff0c;我們可以進行文件的讀取、寫入、刪除等操作。本教程將介紹如何在 No…

工作電壓范圍寬的國產音頻限幅器D2761用于藍牙音箱,輸出噪聲最大僅-90dBV

近年來隨著相關技術的不斷提升&#xff0c;音箱也逐漸從傳統的音箱向智能音箱、無線音箱升級。同時在消費升級的背景下&#xff0c;智能音箱成為人們提升生活品質的方式之一。智能音箱是智能化和語音交互技術的產物&#xff0c;具有點歌、購物、控制智能家居設備等功能&#xf…

python水表識別圖像識別深度學習 CNN

python水表識別&#xff0c;圖像識別深度學習 CNN&#xff0c;Opencv,Keras 重點&#xff1a;項目和文檔是本人近期原創所作&#xff01;程序可以將水表圖片里面的數據進行深度學習&#xff0c;提取相關信息訓練&#xff0c;lw1.3萬字重復15%&#xff0c;可以直接上交那種&…