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   多谢分享......

posted @ 2015-09-06 12:13  天~宇~翱~翔  阅读(3810)  评论(0编辑  收藏  举报