Spring Aop(四)——基於Aspectj註解的Advice介紹
4 基於Aspectj註解的Advice介紹
之前介紹過,Advice一共有五種型別,分別是before、after return、after throwing、after(finally)和around。在使用註解的時候,它們對應的註解分別是@Before、@AfterReturning、@AfterThrowing、@After和@Around。 這幾個註解都是在org.aspectj.lang.annotation包中。
4.1 @Before
Before Advice將在目標方法執行前執行Advice邏輯,通過它我們可以在指定的切入點方法執行前加入特定的邏輯。如下是定義的一個Before Advice,通過其value屬性(註解中只指定value屬性時屬性名是可以省略的)指定其需要攔截的切入點id或name為userService的bean的方法執行,然後攔截後我們只是簡單的列印一條語句,在實際應用中這裡應該加上我們特定的邏輯。
@Before("bean(userService)") public void before() { System.out.println("-----before with pointcut expression: bean(userService)------"); }
注:
- 1、@Before除了可以通過value屬性指定需要攔截的切入點外,還可以指定一個argNames屬性,這個是用於方便我們在Advice中訪問切入點方法引數的,這個在後續會專門用一篇文章來講如何在Advice中使用切入點方法引數。
- 2、argNames這個屬性不僅在@Before上有,在其它的Advice註解上也有。
- 3、除非丟擲異常,否則Before Advice是沒法阻止程式繼續往下執行的。
所有的Advice方法都可以接收一個JoinPoint引數,而且這個引數必須是Advice方法的第一個引數,通過這個引數我們可以獲取到目標方法的一些資訊,比如當前方法呼叫傳遞的引數資訊、目標物件等。而如果是Around型別的Advice則必須接受一個ProceedingJoinPoint型別的引數,ProceedingJoinPoint是JoinPoint的子類。
@Before("bean(userService)") public void before(JoinPoint joinPoint) { System.out.println("-----before with pointcut expression: bean(userService)------"); joinPoint.getArgs();//獲取當前目標方法呼叫傳遞的引數 joinPoint.getSignature();//獲取當前目標方法的簽名,通過它可以獲取到目標方法名 joinPoint.getThis();//獲取AOP生成的代理物件 joinPoint.getTarget();//獲取被代理物件,即目標物件 System.out.println(joinPoint.getArgs()); System.out.println(joinPoint.getSignature().getName()); System.out.println(joinPoint.getThis().getClass()); System.out.println(joinPoint.getTarget().getClass()); System.out.println(joinPoint.toString()); }
4.2 @AfterReturning
AfterReturning Advice對應的是切入點方法正常執行完的攔截,即切入點方法執行時沒有對外丟擲異常,包括在目標方法被Around型別的Advice處理時沒有丟擲異常,如果目標方法在被Around型別的Advice處理時也丟擲了異常,則同樣會被認為目標方法是執行異常的,因為Around Advice是最先處理的,AfterReturning Advice會在Around Advice處理結束後才被觸發的。如果我們希望在AfterReturning Advice中根據目標方法的返回結果做特定的業務邏輯,那麼我們可以給AfterReturning Advice處理方法加一個引數,引數型別可以是你能確定的目標方法返回型別或用通用的Object,然後需要在@AfterReturning上通過returning屬性指定目標方法的返回值需要賦值給AfterReturning Advice處理方法的哪個引數。如下示例中就在Advice處理方法上加入了一個通用型別的Object型別的returnValue引數,然後指定@AfterReturning的returning屬性為“returnValue”。如果我們確定目標方法的返回結果一定是一個User型別的,那麼我們也可以指定下面的方法引數型別是User型別。
@AfterReturning(value="bean(userService)", returning="returnValue") public void afterReturning(Object returnValue) { System.out.println("-----after returning with pointcut expression: bean(userService)------"); System.out.println("-----return value is: " + returnValue); }
4.3 @AfterThrowing
AfterThrowing Advice對應的是切入點方法執行對外丟擲異常的攔截。因為當一個切入點方法可以同時被Around Advice和AfterThrowing Advice攔截時,實際上AfterThrowing Advice攔截的是Around Advice處理後的結果,所以這種情況下最終AfterThrowing Advice是否能被觸發,還要看Around Advice自身是否對外丟擲異常,即算是目標方法對外丟擲了異常,但是被Around Advice處理了又沒有向外丟擲異常的時候AfterThrowing Advice也不會被觸發的。如果希望在AfterThrowing Advice處理方法中獲取到被丟擲的異常,可以給對應的Advice處理方法加一個Exception或其子型別(能確定丟擲的異常型別)的方法引數,然後通過@AfterThrowing的throwing屬性指定攔截到的異常物件對應的Advice處理方法的哪個引數。如下就指定了攔截到的異常物件將傳遞給Advice處理方法的ex引數。
@AfterThrowing(value="bean(userService)", throwing="ex") public void afterThrowing(Exception ex) { System.out.println("-----after throwing with pointcut expression: bean(userService)------" + ex); }
AfterThrowing是用於在切入點方法丟擲異常時進行某些特殊的處理,但是它不會阻止方法呼叫者看到異常結果。
4.4 @After
After Advice就相當於try…catch…finally語句裡面的finally的角色,即無論被攔截的切入點方法是成功執行完成還是對外丟擲了異常,對應的Advice處理方法都將會執行。
@After("bean(userService)") public void after() { System.out.println("-----after with pointcut expression: bean(userService)------"); }
4.5 @Around
Around Advice必須接收一個ProceedingJoinPoint型別的方法引數,然後在方法體中選擇一個合適的時機來呼叫ProceedingJoinPoint的proceed方法以觸發對目標方法的呼叫,然後Around Advice處理方法的返回值會被當做是目標方法呼叫的返回值。所以通過Around Advice我們可以在通過ProceedingJoinPoint呼叫目標方法的前後加上特定的邏輯,包括使用try…catch…finally等,所以Around Advice是功能最強大的一個Advice,前面的任何一種Advice在應用的時候都可以被Around Advice替換。
@Around("bean(userService)") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("-----around with pointcut expression: bean(userService)------"); System.out.println("---------------------呼叫前---------------------"); Object result = pjp.proceed(); System.out.println("---------------------呼叫後---------------------"); return result; }
在上面的示例中我們就通過Around Advice攔截了id或name為userService的bean的所有方法呼叫,把真實的目標方法的返回結果返回去了。而實際上我們這裡還可以修改目標方法的返回結果,比如常用的就是Spring的快取會通過Around Advice在呼叫目標方法前先從快取中獲取結果,如果獲取到了則直接返回。這也是Around Advice跟AfterReturning Advice一個比較大的差別,AfterReturning Advice是不能改變返回物件的引用的,但是它可以改變返回物件的個別屬性。在使用Around Advice時也可以改變目標方法呼叫時傳遞的引數,這個時候要用到ProceedingJoinPoint 的帶引數的proceed(Object[] args)方法了。如下示例中我們就在Around Advice中把呼叫目標方法的引數替換為15了。
@Around("bean(userService)") public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("-----around with pointcut expression: bean(userService)------"); System.out.println("---------------------呼叫前---------------------"); Object[] params = new Object[]{15}; Object result = pjp.proceed(params);//可以調整目標方法呼叫時傳遞的引數 System.out.println("---------------------呼叫後---------------------"); return result; }
4.6 Advice執行順序
官方的說法是在進入切入點前優先順序越高的越先執行,而在從切入點出去時優先順序越高的會越後執行。當一個切面類中定義了多個Advice需要作用於同一個切入點時它們的執行順序是不確定的,理由是無法通過反射獲取到這些Advice在編譯好的位元組碼中的宣告順序,這種情況下官方建議將多種切面邏輯整合到一個Advice中處理,以免造成錯誤。當兩個定義在不同的切面中的Advice需要作用在同一個切入點時,除非你在切面類上使用@Order註解指定了順序,數字越小表示優先順序越高,或者是使切面類實現Ordered介面。以下是官方原文地址。http://docs.spring.io/spring/docs/4.1.0.RELEASE/spring-framework-reference/html/aop.html#aop-ataspectj-advice-ordering
(注:本文是基於Spring4.1.0所寫,寫於2017年1月19日星期四)