一、簡介
AOP主要包含了通知、切點和連接點等術語,介紹如下:
通知(advice)
通知定義了切面是什么以及何時調用,何時調用包含以下幾種
- Before 在方法被調用之前調用通知
- After 在方法完成之后調用通知,無論方法執行是否成功
- After-returning 在方法成功執行之后調用通知
- After-throwing 在方法拋出異常后調用通知
- Around 通知包裹了被通知的方法,在被通知的方法調用之前和調用之后執行自定義的行為
切點(PointCut)
通知定義了切面的什么和何時,切點定義了何處,切點的定義會匹配通知所要織入的一個或多個連接點,
我們通常使用明確的類的方法名稱來指定這些切點,或是利用正則表達式定義匹配的類和方法名稱來指定這些切點。
連接點(JoinPoint)
連接點是在應用執行過程中能夠插入切面的一個點,這個點可以是調用方法時,拋出異常時,甚至是修改一個字段時,
切面代碼可以利用這些連接點插入到應用的正常流程中,并添加新的行為,如日志、安全、事務、緩存等。
-
joinPoint.getSignature().getDeclaringTypeName()
可以獲取到調用方法的類名(包括包名), -
joinPoint.getSignature().getName()
可以獲取方法名, -
Arrays.toString(joinPoint.getArgs())
得到的是方法調用的參數列表, -
joinPoint.proceed()
可以得到方法的返回結果
二、注解開發
聲明一個切面,只需要在類名上添加@Aspect屬性即可,具體的連接點,我們用@Pointcut和@Before、@After等標注。
在聲明前 我們需要依賴配置pom
1
2
3
4
5
6
7
8
9
10
|
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version> 1.6 . 11 </version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version> 1.6 . 11 </version> </dependency> |
例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
package com.ganji.demo.service.aspect; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Service; /** * Created by admin on 2015/9/2. */ @Aspect @Service public class XmlAopDemoUserLog { // 配置切點 及要傳的參數 @Pointcut ( "execution(* com.ganji.demo.service.user.UserService.GetDemoUser(..)) && args(id)" ) public void pointCut( int id) { } // 配置連接點 方法開始執行時通知 @Before ( "pointCut(id)" ) public void beforeLog( int id) { System.out.println( "開始執行前置通知 日志記錄:" +id); } // 方法執行完后通知 @After ( "pointCut(id)" ) public void afterLog( int id) { System.out.println( "開始執行后置通知 日志記錄:" +id); } // 執行成功后通知 @AfterReturning ( "pointCut(id)" ) public void afterReturningLog( int id) { System.out.println( "方法成功執行后通知 日志記錄:" +id); } // 拋出異常后通知 @AfterThrowing ( "pointCut(id)" ) public void afterThrowingLog( int id) { System.out.println( "方法拋出異常后執行通知 日志記錄" +id); } // 環繞通知 @Around ( "pointCut(id)" ) public Object aroundLog(ProceedingJoinPoint joinpoint, int id) { Object result = null ; try { System.out.println( "環繞通知開始 日志記錄" +id); long start = System.currentTimeMillis(); //有返回參數 則需返回值 result = joinpoint.proceed(); long end = System.currentTimeMillis(); System.out.println( "總共執行時長" + (end - start) + " 毫秒" ); System.out.println( "環繞通知結束 日志記錄" ); } catch (Throwable t) { System.out.println( "出現錯誤" ); } return result; } } |
AOP切面中的同步問題
在WebLogAspect切面中,分別通過doBefore和doAfterReturning兩個獨立函數實現了切點頭部和切點返回后執行的內容,
若我們想統計請求的處理時間,就需要在doBefore處記錄時間,并在doAfterReturning處通過當前時間與開始處記錄的時間計算
得到請求處理的消耗時間。
那么我們是否可以在WebLogAspect切面中定義一個成員變量來給doBefore和doAfterReturning一起訪問呢?是否會有同步問題呢?
的確,直接在這里定義基本類型會有同步問題,所以我們可以引入ThreadLocal對象,像下面這樣進行記錄:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@Aspect @Component public class WebLogAspect { private Logger logger = Logger.getLogger(getClass()); ThreadLocal<Long> startTime = new ThreadLocal<>(); @Pointcut ( "execution(public * com.juzi.web..*.*(..))" ) public void webLog(){} @Before ( "webLog()" ) public void doBefore(JoinPoint joinPoint) throws Throwable { startTime.set(System.currentTimeMillis()); // 省略日志記錄內容 } @AfterReturning (returning = "ret" , pointcut = "webLog()" ) public void doAfterReturning(Object ret) throws Throwable { // 處理完請求,返回內容 logger.info( "RESPONSE : " + ret); logger.info( "SPEND TIME : " + (System.currentTimeMillis() - startTime.get())); } } |
AOP切面的優先級
由于通過AOP實現,程序得到了很好的解耦,但是也會帶來一些問題,比如:我們可能會對Web層做多個切面,校驗用戶,
校驗頭信息等等,這個時候經常會碰到切面的處理順序問題。
所以,我們需要定義每個切面的優先級,我們需要@Order(i)注解來標識切面的優先級。i的值越小,優先級越高。
假設我們還有一個切面是CheckNameAspect用來校驗name必須為didi,我們為其設置@Order(10),
而上文中WebLogAspect設置為@Order(5),所以WebLogAspect有更高的優先級,這個時候執行順序是這樣的:
在@Before中優先執行@Order(5)的內容,再執行@Order(10)的內容
在@After和@AfterReturning中優先執行@Order(10)的內容,再執行@Order(5)的內容
所以我們可以這樣子總結:
- 在切入點前的操作,按order的值由小到大執行
- 在切入點后的操作,按order的值由大到小執行
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流。
原文鏈接:https://yangzhiw.github.io/2016/07/28/spring-AOP-注解開發/