@AfterReturning(\n()\
public void doAccessCheck() { // ... }
}说明:你可以在同一个切面里定义多个通知,或者其他成员。我们只是在展示如何定义一个简单的通知。这些例子主要的侧重点是正在讨论的问题。
有时候你需要在通知体内得到返回的值。你可以使用以 @AfterReturning 接口的形式来绑定返回值:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning(
pointcut=\,
returning=\
public void doAccessCheck(Object retVal) { // ... }
}在 returning 属性中使用的名字必须对应于通知方法内的一个参数名。 当一个方法执行返回后,返回值作为相应的参数值传入通知方法。 一个 returning 子句也限制了只能匹配到返回指定类型值的方法。 (在本例子中,返回值是 Object 类,也就是说返回任意类型都会匹配)
6.2.4.3. 抛出后通知(After throwing advice)
抛出后通知在一个方法抛出异常后执行。使用 @AfterThrowing 注解来声明:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing(\()\
public void doRecoveryActions() {
// ... }
}你通常会想要限制通知只在某种特殊的异常被抛出的时候匹配,你还希望可以在通知体内得到被抛出的异常。 使用 throwing 属性不光可以限制匹配的异常类型(如果你不想限制,请使用 Throwable 作为异常类型),还可以将抛出的异常绑定到通知的一个参数上。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing(
pointcut=\,
throwing=\
public void doRecoveryActions(DataAccessException ex) { // ... }
}在 throwing 属性中使用的名字必须与通知方法内的一个参数对应。 当一个方法因抛出一个异常而中止后,这个异常将会作为那个对应的参数送至通知方法。 throwing 子句也限制了只能匹配到抛出指定异常类型的方法(上面的示例为 DataAccessException)。
6.2.4.4. 后通知(After (finally) advice)
不论一个方法是如何结束的,在它结束后(finally)后通知(After (finally) advice)都会运行。 使用 @After 注解来声明。这个通知必须做好处理正常返回和异常返回两种情况。通常用来释放资源。
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.After;
@Aspect
public class AfterFinallyExample {
@After(\ public void doReleaseLock() { // ... }
}6.2.4.5. 环绕通知(Around Advice)
最后一种通知是环绕通知。环绕通知在一个方法执行之前和之后执行。 它使得通知有机会既在一个方法执行之前又在执行之后运行。并且,它可以决定这个方法在什么时候执行,如何执行,甚至是否执行。 环绕通知经常在在某线程安全的环境下,你需要在一个方法执行之前和之后共享某种状态的时候使用。 请尽量使用最简单的满足你需求的通知。(比如如果前置通知(before advice)也可以适用的情况下不要使用环绕通知)。
环绕通知使用 @Around 注解来声明。通知的第一个参数必须是
ProceedingJoinPoint 类型。 在通知体内,调用 ProceedingJoinPoint 的 proceed() 方法将会导致潜在的连接点方法执行。 proceed 方法也可能会被调用并且传入一个 Object[] 对象-该数组将作为方法执行时候的参数。
当传入一个 Object[] 对象的时候,处理的方法与通过AspectJ编译器处理环绕通知略有不同。 对于使用传统AspectJ语言写的环绕通知来说,传入参数的数量必须和传递给环绕通知的参数数量匹配(不是后台的连接点接受的参数数量),并且特定顺序的传入参数代替了将要绑定给连接点的原始值(如果你看不懂不用担心)。 Spring采用的方法更加简单并且更好得和他的基于代理(proxy-based),只匹配执行的语法相适用。 如果你适用AspectJ的编译器和编织器来编译为Spring而写的@AspectJ切面和处理参数,你只需要了解这一区别即可。 有一种方法可以让你写出100%兼容Spring AOP和AspectJ的,我们将会在后续的通知参数(advice parameters)的章节中讨论它。 import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundExample {
@Around(\ public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed(); // stop stopwatch return retVal; }
}方法的调用者得到的返回值就是环绕通知返回的值。 例如:一个简单的缓存切面,如果缓存中有值,就返回该值,否则调用proceed()方法。 请注意proceed可能在通知体内部被调用一次,许多次,或者根本不被调用。
6.2.4.6. 通知参数(Advice parameters)
Spring 2.0 提供了完整的通知类型 - 这意味着你可以在通知签名中声明所需的参数,(就像在以前的例子中我们看到的返回值和抛出异常一样)而不总是
使用Object[]。 我们将会看到如何在通知体内访问参数和其他上下文相关的值。首先让我们看以下如何编写普通的通知以找出正在被通知的方法。
6.2.4.6.1. 访问当前的连接点
任何通知方法可以将第一个参数定义为 org.aspectj.lang.JoinPoint 类型 (环绕通知需要定义为 ProceedingJoinPoint 类型的, 它是 JoinPoint 的一个子类。) JoinPoint 接口提供了一系列有用的方法, 比如 getArgs()(返回方法参数)、getThis()(返回代理对象)、getTarget()(返回目标)、getSignature()(返回正在被通知的方法相关信息)和 toString()(打印出正在被通知的方法的有用信息)。
6.2.4.6.2. 传递参数给通知(Advice)
我们已经看到了如何绑定返回值或者异常(使用后置通知(after returning)和异常后通知(after throwing advice)。 为了可以在通知(adivce)体内访问参数,你可以使用 args 来绑定。 如果在一个参数表达式中应该使用类型名字的地方使用一个参数名字,那么当通知执行的时候对应的参数值将会被传递进来。 可能给出一个例子会更好理解。假使你想要通知(advise)接受某个Account对象作为第一个参数的DAO操作的执行,你想要在通知体内也能访问到account对象,你可以写如下的代码:
@Before(\ \
public void validateAccount(Account account) { // ...
}切入点表达式的 args(account,..) 部分有两个目的: 首先它保证了只会匹配那些接受至少一个参数的方法的执行,而且传入的参数必须是 Account 类型的实例, 其次它使得可以在通知体内通过 account 参数来访问那个account参数。
另外一个办法是定义一个切入点,这个切入点在匹配某个连接点的时候“提供”了一个Account对象, 然后直接从通知中访问那个命名的切入点。你可以这样写:
@Pointcut(\&&\+
\
private void accountDataAccessOperation(Account account) {}
@Before(\public void validateAccount(Account account) { // ..
}如果想要知道更详细的内容,请参阅 AspectJ 编程指南。
代理对象(this)、目标对象(target) 和注解(@within, @target, @annotation, @args)都可以用一种简单格式绑定。 以下的例子展示了如何使用 @Auditable 注解来匹配方法执行,并提取AuditCode。
首先是 @Auditable 注解的定义:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Auditable { AuditCode value(); }
然后是匹配 @Auditable 方法执行的通知:
@Before(\ \
public void audit(Auditable auditable) { AuditCode code = auditable.value(); // ... }
6.2.4.6.3. 决定参数名
绑定在通知上的参数依赖切入点表达式的匹配名,并借此在(通知(advice)和切入点(pointcut))的方法签名中声明参数名。 参数名 无法 通过Java反射来获取,所以Spring AOP使用如下的策略来决定参数名字:
如果参数名字已经被用户明确指定,则使用指定的参数名: 通知(advice)和切入点(pointcut)注解有一个额外的\属性,该属性用来指定所注解的方法的参数名 - 这些参数名在运行时是 可以 访问的。例子如下:
@Before(
value=\ \ argNames=\
public void audit(Auditable auditable) { AuditCode code = auditable.value(); // ... }
如果一个@AspectJ切面已经被AspectJ编译器(ajc)编译过了,那么就不需要再添加 argNames 参数了,因为编译器会自动完成这一工作。
使用 'argNames' 属性有点不那么优雅,所以如果没有指定'argNames' 属性, Spring AOP 会寻找类的debug信息,并且尝试从本地变量表(local variable table)中来决定参数名字。 只要编译的时候使用了debug信息(至少要使用 '-g:vars' ),就可获得这些信息。 使用这个flag编译的结果是: (1)你的代码将能够更加容易的读懂(反向工程), (2)生成的class文件会稍许大一