一、知识点
要在Spring中注册AspectJ aspect, 只需要将它们声明为IoC容器中的Bean实例就行了。在Spring IoC容器中启用AspectJ,容器将自动为匹配AspectJ aspect的Bean创建代理。
用AspectJ注解编写的aspect只是一个带有@Aspect注解的Java类。通知(Advice)是带有—个通知注解的简单Java方法。AspectJ支持5种通知注解:@Before、@After、@AfterRetuming、@AfterThrowing 和@Around。
二、代码示例
(1)前置通知
为了创建在程序特定执行点之前处理横切关注点的前置通知(Before Advice),你可以使用@Before注解,并将切入点表达式作为注解值。
/* * Copyright 2013-2015 */ package com.codeproject.jackie.springrecipesnote.springaop; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; /** * Title: CaculatorLoggingAspect.java * * @author jackie * @since May 3, 2013 9:11:49 PM * @version V1.0 */ @Aspect public class CaculatorLoggingAspect { private Log log = LogFactory.getLog(this.getClass()); @Before("execution(* ArithmeticCalculator.add( . . ))") public void logBefore() { log.info("The method add() begins"); } }
切入点表达式匹配ArithmeticCalculator接口的add()方法的执行。表达式前导的星号匹配任何修饰符(public、protected和private)和任何返回类型。参数列表中的两个点匹配任何数量的参数。
为了注册这个aspect,只需要在IoC容器中声明它的一个Bean实例。如果其他Bean不引用,这个aspect Bean可以是匿名的。
<bean class="com.codeproject.jackie.springrecipesnote.springaop.CaculatorLoggingAspect" />测试类如下:
package com.codeproject.jackie.springrecipesnote.springaop; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * Unit test for Aop Advice. */ public class AdviceTest { private ApplicationContext applicationContext; @Test public void testBeforeAdvice() { applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator)applicationContext.getBean("arithmeticCalculator"); arithmeticCalculator.add(1, 2); arithmeticCalculator.sub(1, 2); arithmeticCalculator.mul(1, 2); arithmeticCalculator.div(1, 2); UnitCalculator unitCalculator = (UnitCalculator) applicationContext.getBean("unitCalculator"); unitCalculator.kilogramToPound(100); unitCalculator.kilometerToMile(100); } }
切入点匹配的执行点称作连接点(Join Point)。在这个术语中,切入点是匹配一组连接点的表达式,而通知是在特定连接点上采取的行动。为了使通知访问当前连接点的细节,可以在通知方法中声明一个JoinPoint类型的参数。然后,可以访问连接点的细节,如方法名称和参数值。现在,可以将类名和方法名改为通配符,扩展切入点以匹配所有方法。
@Before("execution(* *.*( . . ))") public void logBefore(JoinPoint joinPoint) { log.info("The method " + joinPoint.getSignature().getName() + "() begins with " + Arrays.toString(joinPoint.getArgs())) ; }(2)最终通知
最终通知(After Advice)在连接点结束之后执行,不管返回结果还是抛出异常。下面的最终通知记录计算器方法的结束。一个aspect可以包含一个或者多个通知。
@After("execution(* *.*( . . ))") public void logAfter(JoinPoint joinPoint) { log.info("The method " + joinPoint.getSignature().getName() + "() ends") ; }(3)后置通知
最终通知(After Advice)不管连接点正常返冋还是抛出异常都执行。如果你希望仅当连接点返回时记录,应该用后置通知(after returning advice)替换最终通知。
@AfterReturning("execution(* *.*( . . ))") public void logAfterReturning(JoinPoint joinPoint) { log.info("The method " + joinPoint.getSignature().getName() + "() ends") ; }
在后置通知中,可以在注解中添加一个returning属性,访问连接点的返回值。这个属性的值应该是通知方法的参数名称,用于传入返回值。然后,必须用这个名称向通知方法的签名中添加一个参数。在运行时,Spring AOP通过这个参数传入返回值。 还要注意,原来的切入点表达式必须改在pointcut属性中表现。
@AfterReturning(pointcut="execution(* *.*( . . ))", returning="result") public void logAfterReturning(JoinPoint joinPoint, Object result) { log.info("The method " + joinPoint.getSignature().getName() + "() ends with " + result) ; }(4)异常通知
异常通知(after throwing advice)仅当连接点抛出异常时执行。
@AfterThrowing("execution(* *.*( . . ))") public void logAfterThrowing(JoinPoint joinPoint) { log.info("The method " + joinPoint.getSignature().getName() + "()") ; }
类似地,连接点抛出的异常也可以通过为@AfterThrowing注解添加一个throwing属性来访问。Throwable类型是Java语言中所有错误和异常的超类。所以,下面的通知将捕捉连接点抛出的任何错误和异常:
@AfterThrowing(pointcut = "execution(* *.*( . . ))", throwing="e") public void logAfterThrowing(JoinPoint joinPoint, Throwable e) { log.info("An exception " + e + " has been thrown in " + joinPoint.getSignature().getName() + "()") ; }(5)环绕通知
环绕通知(around advice)是所有通知类型中最强大的。它获得连接点的完全控制,这样你可以在一个通知中组合前述通知的所有行动。你甚至可以控制何时以及是否继续原来的连接点的执行。
下面的环绕通知组合了前面创建的前置、后置和异常抛出通知。注意,对于环绕通知,连接点的参数类型必须是ProceedingJoinPoint。它是JoinPoint的一个子接口,允许你控制何时继续原始的连接点。
@Around("execution(* *.*( . . ))") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { log.info("The method " + joinPoint.getSignature().getName() + "() begins with " + Arrays.toString(joinPoint.getArgs())) ; try { Object result = joinPoint.proceed(); log.info("The method " + joinPoint.getSignature().getName() + "() ends with " + result) ; return result; } catch (IllegalArgumentException e) { log.info("An exception " + e + " has been thrown in " + joinPoint.getSignature().getName() + "()") ; throw e; } }
环绕通知类型非常强大和灵活,你甚至可以修改原始参数值并且修改最后返回值。必须非常小心地使用这类通知,因为很容易忘记继续原始的连接点的调用。
提示:选择通知类型的通用原则是使用满足你的要求的最弱小的类型。