AspectJ容许使用注解用于定义切面、切入点和加强处理,而Spring框架则能够识别并根据这些注解来生成AOP代理。Spring只是使用了和AspectJ 5同样的注解,但并无使用AspectJ的编译器或者织入器,底层依然使用SpringAOP来实现,依然是在运行时动态生成AOP代理,所以不须要增长额外的编译,也不须要AspectJ的织入器支持。而AspectJ采用编译时加强,因此AspectJ须要使用本身的编译器来编译Java文件,还须要织入器。java
为了启用Spring对@AspectJ切面配置的支持,并保证Spring容器中的目标Bean被一个或多个切面自动加强,必须在Spring配置文件中配置以下内容(第四、九、十、15行):git
<?xml version="1.0" encoding="utf-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!-- 启动@AspectJ支持 --> <aop:aspectj-autoproxy/> </beans>
所谓自动加强,指的是Spring会判断一个或多个切面是否须要对指定的Bean进行加强,并据此自动生成相应的代理,从而使得加强处理在合适的时候被调用。若是不打算使用XML Schema的配置方式,则应该在Spring配置文件中增长以下片断来启用@AspectJ支持(即上面的<aop:aspectj-autoproxy />和下面建立Bean的方式选择一种便可启用@AspectJ支持):github
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator" />
上面配置的是一个Bean后处理器,该处理器将会为容器中Bean生成AOP代理。spring
为了在Spring应用中启动@AspectJ支持,还须要在用用的类加载路径下增长两个AspectJ库:aspectweaver.jar和aspectjrt.jar,直接使用AspectJ安装路径下的lib目录下的这两个Jar文件便可,固然,也能够在Spring解压缩文件夹的lib/aspectj路径下找到它们。下面是项目内容的截图:express
定义切面Beanapp
当启用了@AspectJ支持后,只要咱们在Spring容器中配置一个带@AspectJ注释的Bean,Spring将会自动识别该Bean,并将该Bean做为切面处理。下面是一个例子:框架
@Aspect public class LogAspect { }
切面类(用@Aspect修饰的类)和其余类同样能够有方法和属性的定义,还可能包括切入点、加强处理的定义。当咱们使用@Aspect来修饰一个Java类后,Spring将不会把该Bean当成组件Bean处理,所以当Spring容器检测到某个Bean使用了@AspectJ标注以后,负责自动加强的后处理Bean将会忽略该Bean,不会对该Bean进行任何加强处理。测试
使用Before加强处理spa
当咱们在一个切面类里使用@Before来标注一个方法时,该方法将做为Before加强处理。使用@Before标注时,一般须要指定一个value属性值,该属性值指定一个切入点表达式(既能够是一个已有的切入点,也能够直接定义切入点表达式),用于指定该加强处理将被织入哪些切入点。看例子:代理
package com.abc.advice; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class BeforeAdviceTest { //匹配com.abc.service下的类中以before开始的方法 @Before("execution(* com.abc.service.*.before*(..))") public void permissionCheck() { System.out.println("模拟权限检查"); } }
上面的程序使用@Aspect修饰了BeforeAdviceTest类,这代表该类是一个切面类,在该贴面里定义了一个permissionCheck方法——这个方法原本没有什么特殊之处,但由于使用了@Before来标注该方法,这就将该方法转换成一个Before加强处理。这个@Before注解中,直接指定了切入点表达式,指定com.abc.service包下的类中以before开始的方法的执行做为切入点。现假设咱们在com.abc.service下有一个这样一个类:
package com.abc.service; import org.springframework.stereotype.Component; @Component public class AdviceManager { //这个方法将被BeforeAdviceTest类的permissionCheck匹配到 public void beforeAdvice() { System.out.println("方法: beforeAdviceTest"); } }
从上面的代码来看,这个AdviceManager是一个纯净的Java类,它丝绝不知道将被谁来加强,也不知道将被进行怎样的加强——正式由于AdviceManager类的这种“无知”,才是AOP的最大魅力:目标类能够被无限的加强。
在Spring配置文件中配置自动搜索Bean组件,配置自动搜索切面类,SpringAOP自动对Bean组件进行加强,下面是Spring配置文件代码:
<?xml version="1.0" encoding="utf-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <!-- 启动@AspectJ支持 --> <aop:aspectj-autoproxy/> <!-- 指定自动搜索Bean组件,自动搜索切面类 --> <context:component-scan base-package="com.abc.service,com.abc.advice"> <context:include-filter type="annotation" expression="org.aspectj.lang.annotation.Aspect" /> </context:component-scan> </beans>
主程序很是简单,经过Spring容器获取AdviceManager Bean,并调用Bean的beforeAdvice方法:
package com.abc.main; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.abc.service.AdviceManager; @SuppressWarnings("resource") public class AOPTest { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); AdviceManager manager = context.getBean(AdviceManager.class); manager.beforeAdvice(); } }
执行主程序,将看到如下结果:
使用Before加强处理只能在目标方法执行以前织入加强,使用Before加强处理无需理会目标方法的执行,因此Before处理没法阻止目标方法的执行。Before加强处理执行时,目标方法还未得到执行机会,因此Before加强处理没法访问目标方法的返回值。
使用AfterReturning加强处理
和使用@Before注解的使用相似,使用@AfterReturning来标注一个AfterReturning加强处理,该处理将在目标方法正常完成后被织入。使用@AfterReturning时能够指定两个属性:
pointcut/value:这两个属性的做用是同样的,都用于指定该切入点对应的切入表达式。一样的,既能够是一个已有的切入点,也能够是直接定义的切入点。当指定了pointcut属性后,value的属性值将会被覆盖
returning:指定一个返回值形参名,加强处理定义的方法能够经过该形参名来访问目标方法的返回值。
在com.abc.advice包下面增长AfterReturningAdviceTest,这个类定义了一个AfterReturning加强处理:
package com.abc.advice; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; @Aspect public class AfterReturningAdviceTest { //匹配com.abc.service下的类中以afterReturning开始的方法 @AfterReturning(returning="returnValue", pointcut="execution(* com.abc.service.*.afterReturning(..))") public void log(Object returnValue){ System.out.println("目标方法返回值:" + returnValue); System.out.println("模拟日志记录功能..."); } }
并在AdviceManager类中增长如下内容:
//将被AfterReturningAdviceTest的log方法匹配 public String afterReturning() { System.out.println("方法:afterReturning"); return "afterReturning方法"; }
正如上面程序中看到的,程序中使用@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加强处理:
package com.abc.advice; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; @Aspect public class AfterThrowingAdviceTest { @AfterThrowing(throwing="ex", pointcut="execution(* com.abc.service.*.afterThrow*(..))") public void handleException(Throwable ex) { System.out.println("目标方法抛出异常:" +ex); System.out.println("模拟异常处理"); } }
并在AdviceManager类中增长如下内容:
//将被AfterThrowingAdviceTest的handleException方法匹配 public void afterThrowing() { System.out.println("方法: afterThrowing"); try { int a = 10 / 0; } catch (ArithmeticException ae) { System.out.println("算术异常已被处理"); } String s = null; System.out.println(s.substring(0,3)); }
正如上面程序中看到的,程序中使用@AfterThrowing注解时,指定了一个throwing属性,该属性的值是ex,这代表容许在加强方法handleException中使用名为ex的形参,该形参表明目标方法的抛出的异常对象。运行测试类,能够看到如下结果:
须要注意的是:若是一个异常在程序内部已经处理,那么Spring AOP将不会处理该异常。只有当目标方法抛出一个未处理的异常时,该异常将会做为对应的形参传给加强处理的方法。和AfterReturning相似的是,正确方法的参数类型能够限定切点只匹配指定类型的异常——假如上面的handleException方法的参数类型为NullPointerException,那么若是目标方法只抛出了ArithmaticException,则Spring AOP将不会处理这个异常。固然,handleException的参数类型为Throwable,则匹配了全部的Exception。
从测试结果中能够看到,AfterThrowing处理虽然能够对目标方法的异常进行处理,但这种处理与直接使用catch捕捉不一样:catch捕捉意味着彻底处理该异常,若是catch块中没有从新抛出新异常,则该方法能够正常结束;而AfterThrowing处理虽然处理了该异常,但它不能彻底处理该异常,这个异常依然会传播到上一级调用者(本例中为JVM,故会致使程序终止)。
《Spring中的AOP系列3、4、五》的代码在这里:点击下载,欢迎留言提意见。
【未完,待续】