要在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;
}
}
环绕通知类型非常强大和灵活,你甚至可以修改原始参数值并且修改最后返回值。必须非常小心地使用这类通知,因为很容易忘记继续原始的连接点的调用。
提示:选择通知类型的通用原则是使用满足你的要求的最弱小的类型。
Spring攻略学习笔记(3.02)------用AspectJ注解声明Aspects
基于Annotation的Spring AOP: @AfterThrowing