标签:
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[]参数值将作为目标方法的参数,如果这个数组长度与目标方法的参数个数不等,或者数组元素的类型和目标方法的参数类型不匹配,程序就会出现异常
转自:http://my.oschina.net/itblog/blog/210718 多谢分享......
标签:
原文地址:http://www.cnblogs.com/sunfie/p/4785162.html