Spring學習之Aop的各種增強方法


  AspectJ允許使用注解用於定義切面、切入點和增強處理,而Spring框架則可以識別並根據這些注解來生成AOP代理。Spring只是使用了和AspectJ 5一樣的注解,但並沒有使用AspectJ的編譯器或者織入器,底層依然使用SpringAOP來實現,依然是在運行時動態生成AOP代理,因此不需要增加額外的編譯,也不需要AspectJ的織入器支持。而AspectJ采用編譯時增強,所以AspectJ需要使用自己的編譯器來編譯Java文件,還需要織入器。

    為了啟用Spring對@AspectJ切面配置的支持,並保證Spring容器中的目標Bean被一個或多個切面自動增強,必須在Spring配置文件中配置如下內容(第4、9、10、15行):

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4     xmlns:aop="http://www.springframework.org/schema/aop"
 5          xmlns:context="http://www.springframework.org/schema/context"
 6     xsi:schemaLocation="
 7         http://www.springframework.org/schema/beans 
 8         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
 9         http://www.springframework.org/schema/aop 
10         http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
11         http://www.springframework.org/schema/context
12             http://www.springframework.org/schema/context/spring-context-3.0.xsd">
13              
14     <!-- 啟動@AspectJ支持 -->
15     <aop:aspectj-autoproxy/>
16 </beans>

  所謂自動增強,指的是Spring會判斷一個或多個切面是否需要對指定的Bean進行增強,並據此自動生成相應的代理,從而使得增強處理在合適的時候被調用。如果不打算使用XML Schema的配置方式,則應該在Spring配置文件中增加如下片段來啟用@AspectJ支持(即上面的<aop:aspectj-autoproxy />和下面創建Bean的方式選擇一種即可啟用@AspectJ支持):

1 <bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />

上面配置的是一個Bean后處理器,該處理器將會為容器中Bean生成AOP代理。

    為了在Spring應用中啟動@AspectJ支持,還需要在用用的類加載路徑下增加兩個AspectJ庫:aspectweaver.jar和aspectjrt.jar,直接使用AspectJ安裝路徑下的lib目錄下的這兩個Jar文件即可,當然,也可以在Spring解壓縮文件夾的lib/aspectj路徑下找到它們。下面是項目內容的截圖:

 

定義切面Bean

    當啟用了@AspectJ支持后,只要我們在Spring容器中配置一個帶@AspectJ注釋的Bean,Spring將會自動識別該Bean,並將該Bean作為切面處理。下面是一個例子:

 1 package com.abc.advice;
 2 
 3 import org.aspectj.lang.ProceedingJoinPoint;
 4 import org.aspectj.lang.annotation.After;
 5 import org.aspectj.lang.annotation.AfterReturning;
 6 import org.aspectj.lang.annotation.AfterThrowing;
 7 import org.aspectj.lang.annotation.Around;
 8 import org.aspectj.lang.annotation.Aspect;
 9 import org.aspectj.lang.annotation.Before;
10  
11 @Aspect
12 public class BeforeAdviceTest {
13     
14     @Before("execution(* com.abc.service.*.before*(..))")
15     public void permissionCheck() {
16         System.out.println("模擬權限檢查");
17     }
18     
19     @After("execution(* com.abc.servie.*.afterAdvice*(..))")
20     public void returnCheck(){
21         System.out.println("返回之后進行檢查");
22     }
23     
24     //匹配com.abc.service下的類中以afterReturning開始的方法
25     @AfterReturning(returning="returnValue", 
26        pointcut="execution(* com.abc.service.*.afterReturning(..))")
27     public void log(Object returnValue){
28         System.out.println("目標方法返回值:" + returnValue);
29         System.out.println("模擬日志記錄功能...");
30     }
31     
32     @AfterThrowing(throwing="ex",
33         pointcut="execution(* com.abc.service.*.afterThrow*(..))")
34         public void handleException(Throwable ex) {
35             System.out.println("目標方法拋出異常:" +ex);
36             System.out.println("模擬異常處理");
37         }
38 }

  切面類(用@Aspect修飾的類)和其他類一樣可以有方法和屬性的定義,還可能包括切入點、增強處理的定義。當我們使用@Aspect來修飾一個Java類后,Spring將不會把該Bean當成組件Bean處理,因此當Spring容器檢測到某個Bean使用了@AspectJ標注之后,負責自動增強的后處理Bean將會忽略該Bean,不會對該Bean進行任何增強處理。

使用Before增強處理

    當我們在一個切面類里使用@Before來標注一個方法時,該方法將作為Before增強處理。使用@Before標注時,通常需要指定一個value屬性值,該屬性值指定一個切入點表達式(既可以是一個已有的切入點,也可以直接定義切入點表達式),用於指定該增強處理將被織入哪些切入點。看例子:

1 @Aspect
2 public class BeforeAdviceTest {    
3     @Before("execution(* com.abc.service.*.before*(..))")
4     public void permissionCheck() {
5         System.out.println("模擬權限檢查");
6     }
7 }

  上面的程序使用@Aspect修飾了BeforeAdviceTest類,這表明該類是一個切面類,在該切面里定義了一個permissionCheck方法——這個方法本來沒有什么特殊之處,但因為使用了@Before來標注該方法,這就將該方法轉換成一個Before增強處理。這個@Before注解中,直接指定了切入點表達式,指定com.abc.service包下的類中以before開始的方法的執行作為切入點。現假設我們在com.abc.service下有一個這樣一個類:

 1 package com.abc.service;
 2 import org.springframework.stereotype.Component;
 3  
 4 @Component("adviceManager")
 5 public class AdviceManager {
 6     //這個方法將被BeforeAdviceTest類的permissionCheck匹配到
 7     public void beforeAdvice() {
 8         System.out.println("測試前置增強方法……");
 9     }
10 }

從上面的代碼來看,這個AdviceManager是一個純凈的Java類,它絲毫不知道將被誰來增強,也不知道將被進行怎樣的增強——正式因為AdviceManager類的這種“無知”,才是AOP的最大魅力:目標類可以被無限的增強。

    在Spring配置文件中配置自動搜索Bean組件,配置自動搜索切面類,SpringAOP自動對Bean組件進行增強,下面是Spring配置文件代碼:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4     xmlns:aop="http://www.springframework.org/schema/aop"
 5     xmlns:context="http://www.springframework.org/schema/context"
 6     xsi:schemaLocation="
 7         http://www.springframework.org/schema/beans 
 8         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
 9         http://www.springframework.org/schema/aop 
10         http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
11         http://www.springframework.org/schema/context
12         http://www.springframework.org/schema/context/spring-context-3.0.xsd">
13              
14     <!-- 啟動@AspectJ支持 -->
15     <aop:aspectj-autoproxy/>
16      
17     <!-- 指定自動搜索Bean組件,自動搜索切面類 -->
18     <context:component-scan base-package="com.abc.service,com.abc.advice">
19         <context:include-filter type="annotation" 
20             expression="org.aspectj.lang.annotation.Aspect" />
21     </context:component-scan>
22 </beans>

主程序非常簡單,通過Spring容器獲取AdviceManager Bean,並調用Bean的beforeAdvice方法:

 1 package com.abc.main;
 2  
 3 import org.springframework.context.ApplicationContext;
 4 import org.springframework.context.support.ClassPathXmlApplicationContext;
 5  
 6 import com.abc.service.AdviceManager;
 7  
 8 @SuppressWarnings("resource")
 9 public class AOPTest {
10     public static void main(String[] args) {
11         ApplicationContext context = 
12             new ClassPathXmlApplicationContext("applicationContext.xml");
13         AdviceManager manager = context.getBean(AdviceManager.class);
14         manager.beforeAdvice();
15     }
16 }

執行主程序,將看到以下結果:

    使用Before增強處理只能在目標方法執行之前織入增強,使用Before增強處理無需理會目標方法的執行,所以Before處理無法阻止目標方法的執行。Before增強處理執行時,目標方法還未獲得執行機會,所以Before增強處理無法訪問目標方法的返回值。

使用AfterReturning增強處理

    和使用@Before注解的使用類似,使用@AfterReturning來標注一個AfterReturning增強處理,該處理將在目標方法正常完成后被織入。使用@AfterReturning時可以指定兩個屬性:

  • pointcut/value:這兩個屬性的作用是一樣的,都用於指定該切入點對應的切入表達式。同樣的,既可以是一個已有的切入點,也可以是直接定義的切入點。當指定了pointcut屬性后,value的屬性值將會被覆蓋

  • returning:指定一個返回值形參名,增強處理定義的方法可以通過該形參名來訪問目標方法的返回值。

    在com.abc.advice包下面增加AfterReturningAdviceTest,這個類定義了一個AfterReturning增強處理:

 1 package com.abc.advice;
 2 import org.aspectj.lang.annotation.AfterReturning;
 3 import org.aspectj.lang.annotation.Aspect;
 4 @Aspect
 5 public class AfterReturningAdviceTest {
 6     //匹配com.abc.service下的類中以afterReturning開始的方法
 7     @AfterReturning(returning="returnValue", 
 8         pointcut="execution(* com.abc.service.*.afterReturning(..))")
 9     public void log(Object returnValue){
10         System.out.println("目標方法返回值:" + returnValue);
11         System.out.println("模擬日志記錄功能...");
12     }
13 }

注意在@AfterReturning注解中必須增添returning="returnValue"這個屬性參數,由於所要增強的方法中帶有返回值,如果缺少這個屬性參數,后台會報出:0 formal unbound in pointcut錯誤

並在AdviceManager類中增加以下內容:

1 //將被AfterReturningAdviceTest的log方法匹配
2 public String afterReturning() {
3     System.out.println("方法:afterReturning");
4     return "afterReturning方法";
5 }

  正如上面程序中看到的,程序中使用@AfterReturning注解時,指定了一個returning屬性,該屬性的返回值是returnValue,這表明允許在增強方法log中使用名為returnValue的形參,該形參代表目標方法的返回值。在測試類AOPTest的main方法中增加調用本方法的語句,運行測試類,可以看到以下結果:

    @AfterReturning注解的returning屬性所指定的形參名必須對應增強處理中的一個形參名,當目標方法執行以后,返回值作為相應的參數傳入給增強處理方法。

    需要注意的是,使用@AfterReturning屬性還有一個額外的作用,它可用於限定切入點之匹配具有對應返回值類型的方法——假設上面的log方法的參數returnValue的類型為String,那么該切入點只匹配com.abc.service.impl包下的返回值為String的所有方法。當然,上面的log方法返回值類型為Object,表明該切入點可匹配任何返回值的方法。除此之外,雖然AfterReturning增強處理可以訪問到目標方法的返回值,但它不可改變這個返回值。

使用AfterThrowing增強處理

    使用@AfterThrowing注解可用於標注一個AfterThrowing增強處理,這個處理主要用於處理陳旭中未處理的異常。使用這個注解時可以指定兩個屬性:

  • pointcut/value:這兩個屬性的作用是一樣的,都用於指定該切入點對應的切入表達式。同樣的,既可以是一個已有的切入點,也可以是直接定義的切入點。當指定了pointcut屬性后,value的屬性值將會被覆蓋

  • throwing:指定一個返回值形參名,增強處理定義的方法可通過該形參名來訪問目標方法中所拋出的異常對象。

     在com.abc.advice包下面增加AfterThrowingAdviceTest,這個類定義了一個AfterThrowing增強處理:

 1 package com.abc.advice;
 2  
 3 import org.aspectj.lang.annotation.AfterThrowing;
 4 import org.aspectj.lang.annotation.Aspect;
 5  
 6 @Aspect
 7 public class AfterThrowingAdviceTest {
 8     @AfterThrowing(throwing="ex",
 9         pointcut="execution(* com.abc.service.*.afterThrow*(..))")
10     public void handleException(Throwable ex) {
11         System.out.println("目標方法拋出異常:" +ex);
12         System.out.println("模擬異常處理");
13     }
14 }
 1 //將被AfterThrowingAdviceTest的handleException方法匹配
 2 public void afterThrowing() {
 3     System.out.println("方法: afterThrowing");
 4     try {
 5         int a = 10 / 0;
 6     } catch (ArithmeticException ae) {
 7         System.out.println("算術異常已被處理");
 8     }
 9     String s = null;
10     System.out.println(s.substring(0,3));
11 }

正如上面程序中看到的,程序中使用@AfterThrowing注解時,指定了一個throwing屬性,該屬性的值是ex,這表明允許在增強方法log中使用名為ex的形參,該形參代表目標方法的拋出的異常對象。運行測試類,可以看到以下結果:

    需要注意的是:如果一個異常在程序內部已經處理,那么Spring AOP將不會處理該異常。只有當目標方法拋出一個未處理的異常時,該異常將會作為對應的形參傳給增強處理的方法。和AfterReturning類似的是,正確方法的參數類型可以限定切點只匹配指定類型的異常——假如上面的handleException方法的參數類型為NullPointerException,那么如果目標方法只拋出了ArithmaticException,則Spring AOP將不會處理這個異常。當然,handleException的參數類型為Throwable,則匹配了所有的Exception。

    從測試結果中可以看到,AfterThrowing處理雖然可以對目標方法的異常進行處理,但這種處理與直接使用catch捕捉不同:catch捕捉意味着完全處理該異常,如果catch塊中沒有重新拋出新異常,則該方法可以正常結束;而AfterThrowing處理雖然處理了該異常,但它不能完全處理該異常,這個異常依然會傳播到上一級調用者(本例中為JVM,故會導致程序終止)。

 

使用After增強處理

 

    Spring還提供了一個After增強處理,它與AfterReturning優點類似,但也有區別:

 

  • AfterReturning增強處理只有在目標方法正確完成后才會被織入

  • After增強處理不管目標方法如何結束(正確還是異常),它都會被織入

 

    正是因為這個特點,因此After增強處理必須准備處理正常返回和異常返回兩種情況,這種增強處理通常用於釋放資源。使用@After注解標注一個方法,即可將該方法轉換為After增強處理。使用@After注解是需要指定一個value屬性,用於指定該增強處理的切入點,既可以是一個已有的切入點,也可以直接定義切入點表達式。

 

    在com.abc.advice包下面增加AfterAdviceTest,這個類定義了一個After增強處理:

1 @Aspect
2 public class AfterAdviceTest {
3     @After(value="execution(* com.abc.servie.impl.*.afterAdvice*(..))")
4     public void releaseResource() {
5         System.out.println("模擬釋放數據庫連接");
6     }
7 }
1 //將被AfterAdvice的releaseResource方法匹配
2 public void afterAdvice() {
3     System.out.println("方法: afterAdvice");
4 }

上面定義了一個After增強處理,不管切入點的目標方法如何結束,該增強處理都會被織入。下面是測試結果:

 

使用Around增強處理

    @Around注解用於標注Around增強處理,它近似等於Before增強處理和AfterReturning增強處理的總和,Around增強處理既可以在執行目標方法前織入增強動作,也可以在目標方法之后織入增強動作。

    與@Before和@AfterReturning不同的是,@Around甚至可以決定目標方法在什么時候執行,如何執行,甚至可以完全阻止目標方法的執行。@Around可以修改目標方法的參數值,也可以修改目標方法的返回值。

    @Around的功能雖然強大,但通常需要在線程安全的環境下使用,因此,如果使用普通的@Before和@AfterReturning就能解決的問題,就沒有必要使用@Around了。如果需要目標方法執行之前和執行之后共享某種數據狀態,則應該考慮使用@Around;尤其是需要使用增強處理阻止目標方法的執行,或者需要改變目標方法的參數和執行后的返回值時,就只能使用@Around了。

    可以想象,使用@Around時,也需要指定一個value屬性,這個屬性依然是用於指定切入點。另外,當定義一個Around增強處理時,該方法的第一個形參必須是ProceedingJoinPoint類型(就是說至少包含一個形參),在增強處理方法體內,調用ProceedingJoinPoint的proceed()方法才會執行目標方法——這就是Around增強處理可以完全控制目標方法的執行時機、如何執行的關鍵,如果增強處理的方法體內沒有調用這個proceed()方法,則目標方法不會執行。

    調用proceed()方法時,還可以傳入一個Object[]對象,該數組中的值將被傳入目標方法作為執行方法的實參。因此我們可以通過這個參數,修改方法的參數值。

    在com.abc.advice包下面增加AroundAdviceTest,這個類定義了一個Around增強處理:

 1 package com.abc.advice;
 2  
 3 import org.aspectj.lang.ProceedingJoinPoint;
 4 import org.aspectj.lang.annotation.Around;
 5 import org.aspectj.lang.annotation.Aspect;
 6  
 7 @Aspect
 8 public class AroundAdviceTest {
 9     @Around(value="execution(* com.abc.service.*.around*(..))")
10     public Object process(ProceedingJoinPoint point) throws Throwable {
11         System.out.println("模擬執行目標方法前的增強處理:事務開始...");
12         //修改目標方法的參數
13         String[] params = new String[]{"param1"};
14         //執行目標方法,並保存目標方法執行后的返回值
15         Object returnValue = point.proceed(params);
16         System.out.println("模擬執行目標方法后的增強處理:事務結束...");
17         //返回修改后的返回值
18         return "方法實際返回值:" + returnValue + ",這是返回值的后綴";
19     }
20 }

上面定義了一個AroundAdviceTest切面,該切面包含了一個Around增強處理:process()方法,該方法中第一行代碼用於模擬調用目標方法之前的處理,第二行修改了目標方法的第一個參數,接下來調用目標方法,后面模擬調用目標方法之后的處理和對返回值的修改。正如前面說的,通過這個process方法,我們可以增加類似於@Before和@AfterReturning的增強處理,可以決定什么時候執行目標方法,可以修改目標方法的參數值,還可以修改目標方法的返回值,真是想做什么就做什么啊!

    在AdviceManager類中增加以下內容:

1 //將被AroundAdvice的process方法匹配
2 public String aroundAdvice(String param1) {
3     System.out.println("方法: aroundAdvice");
4     return param1;
5 }

執行測試類,結果如下:

    

    需要注意的是,當調用ProceedingJoinPoint的proceed()方法時,傳入的Object[]參數值將作為目標方法的參數,如果這個數組長度與目標方法的參數個數不等,或者數組元素的類型和目標方法的參數類型不匹配,程序就會出現異常

獲取目標方法的信息

    訪問目標方法最簡單的做法是定義增強處理方法時,將第一個參數定義為JoinPoint類型,當該增強處理方法被調用時,該JoinPoint參數就代表了織入增強處理的連接點。JoinPoint里包含了如下幾個常用的方法:

  • Object[] getArgs:返回目標方法的參數

  • Signature getSignature:返回目標方法的簽名

  • Object getTarget:返回被織入增強處理的目標對象

  • Object getThis:返回AOP框架為目標對象生成的代理對象

    注意:當使用@Around處理時,我們需要將第一個參數定義為ProceedingJoinPoint類型,該類是JoinPoint的子類。

    下面的切面類(依然放在com.abc.advice包中)中定義了Before、Around、AfterReturning和After 4中增強處理,並分別在4種增強處理中訪問被織入增強處理的目標方法、目標方法的參數和被織入增強處理的目標對象等:

 

 1 package com.abc.advice;
 2  
 3 import java.util.Arrays;
 4  
 5 
 6 import org.aspectj.lang.JoinPoint;
 7 import org.aspectj.lang.ProceedingJoinPoint;
 8 import org.aspectj.lang.annotation.After;
 9 import org.aspectj.lang.annotation.AfterReturning;
10 import org.aspectj.lang.annotation.Around;
11 import org.aspectj.lang.annotation.Aspect;
12 import org.aspectj.lang.annotation.Before;
13  
14 @Aspect
15 public class AdviceTest {
16     @Around("execution(* com.abc.service.*.many*(..))")
17     public Object process(ProceedingJoinPoint point) throws Throwable {
18         System.out.println("-----------------@Around環繞增強開始---------------------");
19         
20         System.out.println("調用@Around環繞增強:在目標方法之前調用...");
21         //訪問目標方法的參數:
22         Object[] args = point.getArgs();
23         System.out.println("目標方法中的參數為:");
24         for(Object var : args){
25             System.out.print(var.toString()+",");
26         }
27         if (args != null && args.length > 0 && args[0].getClass() == String.class) {
28             //修改目標方法中的參數
29             args[0] = "目標方法中的參數被我環繞增強方法修改了,修改為:around";
30         }
31         //用改變后的參數執行目標方法
32         Object returnValue = point.proceed(args);
33         System.out.println("在目標方法之后調用@Around環繞增強方法...");
34         System.out.println("@Around環繞增強方法被織入的目標對象為:" + point.getTarget());
35         System.out.println("-----------------@Around環繞增強結束---------------------");
36         return "目標方法原始的返回值:" + returnValue;
37     }
38      
39     @Before("execution(* com.abc.service.*.many*(..))")
40     public void permissionCheck(JoinPoint point) {
41         System.out.println("-------------@Before開始---------------------");
42         System.out.println("調用@Before前置增強方法模擬權限檢查...");
43         System.out.println("前置增強方法@Before所增強的全類名為::" + point.getSignature().getDeclaringTypeName() + 
44                 "增強的方法名為:" + point.getSignature().getName());
45         System.out.println("@Before前置增強方法的參數為:" + Arrays.toString(point.getArgs()));
46         System.out.println("@Before前置增強方法被織入的目標對象為:" + point.getTarget());
47         System.out.println("-------------@Before結束---------------------");
48     }
49      
50     @AfterReturning(pointcut="execution(* com.abc.service.*.many*(..))", 
51         returning="returnValue")
52     public void log(JoinPoint point, Object returnValue) {
53         System.out.println("------------@AfterReturning開始---------------------");
54         
55         System.out.println("@AfterReturning:模擬日志記錄功能...");
56         System.out.println("@AfterReturning:目標方法為:" + 
57                 point.getSignature().getDeclaringTypeName() + 
58                 "." + point.getSignature().getName());
59         System.out.println("@AfterReturning:參數為:" + 
60                 Arrays.toString(point.getArgs()));
61         System.out.println("@AfterReturning:返回值為:" + returnValue);
62         System.out.println("@AfterReturning:被織入的目標對象為:" + point.getTarget());
63         System.out.println("---------------@AfterReturning結束----------------------");
64          
65     }
66 //     
67     @After("execution(* com.abc.service.*.many*(..))")
68     public void releaseResource(JoinPoint point) {
69         System.out.println("------------@After開始---------------------");
70         System.out.println("@After:模擬釋放資源...");
71         System.out.println("@After:目標方法為:" + 
72                 point.getSignature().getDeclaringTypeName() + 
73                 "." + point.getSignature().getName());
74         System.out.println("@After:參數為:" + Arrays.toString(point.getArgs()));
75         System.out.println("@After:被織入的目標對象為:" + point.getTarget());
76         System.out.println("------------@After結束---------------------");
77     }
82 }

 

在AdviceManager類中增加以下內容:

1 public String manyAdvices(String param1, String param2) {
2     System.out.println("方法:manyAdvices");
3     return param1 + " 、" + param2;
4 }

在com.abc.main.AOPTest中加入方法的調用,觸發切點:

1 String result = manager.manyAdvices("aa", "bb");
2 System.out.println("Test方法中調用切點方法的返回值:" + result);

 執行結果如下:

 
         

-------------@Before開始---------------------
調用@Before前置增強方法模擬權限檢查...
前置增強方法@Before所增強的全類名為::com.abc.service.AdviceManager增強的方法名為:manyAdvices
@Before前置增強方法的參數為:[sunfei, lisi]
@Before前置增強方法被織入的目標對象為:1d050be
-------------@Before結束---------------------
-----------------@Around環繞增強開始---------------------
調用@Around環繞增強:在目標方法之前調用...
目標方法中的參數為:
sunfei,lisi,方法:manyAdvices
------------@After開始---------------------
@After:模擬釋放資源...
@After:目標方法為:com.abc.service.AdviceManager.manyAdvices
@After:參數為:[sunfei, lisi]
@After:被織入的目標對象為:1d050be
------------@After結束---------------------
------------@AfterReturning開始---------------------
@AfterReturning:模擬日志記錄功能...
@AfterReturning:目標方法為:com.abc.service.AdviceManager.manyAdvices
@AfterReturning:參數為:[sunfei, lisi]
@AfterReturning:返回值為:目標方法中的參數被我環繞增強方法修改了,修改為:around 、lisi
@AfterReturning:被織入的目標對象為:1d050be
---------------@AfterReturning結束----------------------
在目標方法之后調用@Around環繞增強方法...
@Around環繞增強方法被織入的目標對象為:1d050be
-----------------@Around環繞增強結束---------------------
Test方法中調用切點方法的返回值:目標方法原始的返回值:目標方法中的參數被我環繞增強方法修改了,修改為:around 、lisi


@AfterReturning:被織入的目標對象為:com.abc.service.AdviceManager@1dfc617e
Test方法中調用切點方法的返回值:原返回值:改變后的參數1 、bb,這是返回結果的后綴

  從結果中可以看出:在任何一個織入的增強處理中,都可以獲取目標方法的信息。另外,Spring AOP采用和AspectJ一樣的有限順序來織入增強處理:在“進入”連接點時,最高優先級的增強處理將先被織入(所以給定的兩個Before增強處理中,優先級高的那個會先執行);在“退出”連接點時,最高優先級的增強處理會最后被織入(所以給定的兩個After增強處理中,優先級高的那個會后執行)。當不同的切面中的多個增強處理需要在同一個連接點被織入時,Spring AOP將以隨機的順序來織入這些增強處理。如果應用需要指定不同切面類里的增強處理的優先級,Spring提供了如下兩種解決方案:

  • 讓切面類實現org.springframework.core.Ordered接口:實現該接口只需要實現一個int getOrder()方法,該方法返回值越小,優先級越高

  • 直接使用@Order注解來修飾一個切面類:使用這個注解時可以配置一個int類型的value屬性,該屬性值越小,優先級越高

    優先級高的切面類里的增強處理的優先級總是比優先級低的切面類中的增強處理的優先級高。例如:優先級為1的切面類Bean1包含了@Before,優先級為2的切面類Bean2包含了@Around,雖然@Around優先級高於@Before,但由於Bean1的優先級高於Bean2的優先級,因此Bean1中的@Before先被織入。

    同一個切面類里的兩個相同類型的增強處理在同一個連接點被織入時,Spring AOP將以隨機的順序來織入這兩個增強處理,沒有辦法指定它們的織入順序。如果確實需要保證它們以固有的順序被織入,則可以考慮將多個增強處理壓縮為一個增強處理;或者將不同增強處理重構到不同切面中,通過在切面級別上定義順序。

    如果只要訪問目標方法的參數,Spring還提供了一種更加簡潔的方法:我們可以在程序中使用args來綁定目標方法的參數。如果在一個args表達式中指定了一個或多個參數,該切入點將只匹配具有對應形參的方法,且目標方法的參數值將被傳入增強處理方法。下面輔以例子說明:

 1 package com.abc.advice;
 2  
 3 import java.util.Date;
 4 import org.aspectj.lang.annotation.AfterReturning;
 5 import org.aspectj.lang.annotation.Aspect;
 6  
 7 @Aspect
 8 public class AccessArgAdviceTest {
 9     @AfterReturning(
10             pointcut="execution(* com.abc.service.*.access*(..)) && args(time, name)",
11             returning="returnValue")
12     public void access(Date time, Object returnValue, String name) {
13         System.out.println("目標方法中的參數String = " + name);
14         System.out.println("目標方法中的參數Date = " + time);
15         System.out.println("目標方法的返回結果returnValue = " + returnValue);
16     }
17 }

上面的程序中,定義pointcut時,表達式中增加了args(time, name)部分,意味着可以在增強處理方法(access方法)中定義time和name兩個屬性——這兩個形參的類型可以隨意指定,但一旦指定了這兩個參數的類型,則這兩個形參類型將用於限制該切入點只匹配第一個參數類型為Date,第二個參數類型為name的方法(方法參數個數和類型若有不同均不匹配)。

    注意,在定義returning的時候,這個值(即上面的returning="returnValue"中的returnValue)作為增強處理方法的形參時,位置可以隨意,即:如果上面access方法的簽名可以為

1 public void access(Date time, Object returnValue, String name)
2 //也可以為
3 public void access(Object returnValue, Date time, String name)
4 //還可以為
5 public void access(Date time, String name, Object returnValue)

只需要滿足另外的參數名的順序和pointcut中args(param1, param2)的順序相同即可。我們在AdviceManager中定義一個方法,該方法的第一個參數為Date類型,第二個參數為String類型,該方法的執行將觸發上面的access方法,如下:

1 //將被AccessArgAdviceTest的access方法匹配
2 public String accessAdvice(Date d, String n) {
3     System.out.println("方法:accessAdvice");
4     return "aa";
5 }

在AOPTest中增加調用這個accessAdvice方法並執行,下面是輸出結果:

    從執行結果可以看出,使用args表達式有如下兩個作用:

  • 提供了一種簡單的方式來訪問目標方法的參數

  • 可用於對切入點表達式作額外的限制

    除此之外,使用args表達式時,還可以使用如下形式:args(param1, param2, ..),注意args參數中后面的兩個點,它表示可以匹配更多參數。在例子args(param1, param2, ..)中,表示目標方法只需匹配前面param1和param2的類型即可。

 

轉自:http://my.oschina.net/itblog/blog/210718   多謝分享......


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM