概述
【IT168 专稿】在《基于@AspectJ配置Spring AOP(之一)》的文章中,我们讲解基于@AspectJ Spring AOP的基础知识,在本文中,我们将继续学习@AspectJ一些高级的知识。@AspectJ可以使用逻辑运算符对切点进行复合运算得到复合的切点;为了在切面中重用切点,我们还可以对切点进行命名,以便在其它的地方引用定义过的切点;当一个连接点匹配多个切点时,需要考虑织入顺序的问题;此外,一个重要的问题是如何在增强中访问连接点上下文的信息。
切点复合运算
使用切点复合运算符,我们将拥有强大而灵活的切点表达能力,以下是一个使用了复合切点的切面:
代码清单 7 TestAspect:切点复合运算
在①处,我们通过&&运算符定义了一个匹配com.baobaotao包中所有greetTo方法的切点;在②处,我们通过!和&&运算符定义了一个匹配所有serveTo()方法并且该方法不位于NaiveWaiter目标类的切点;在③处,我们通过||运算符定义了一个匹配Waiter和Seller接口实现类所有连接点的切点。package com.baobaotao.aspectj.advanced;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class TestAspect ...{
@After("within(com.baobaotao.*) "
+ " && execution(* greetTo(..)))") ①与运算
public void greeToFun() ...{
System.out.println("--greeToFun() executed!--");
}
![]()
@Before(" !target(com.baobaotao.NaiveWaiter) "+
"&& execution(* serveTo(..)))") ②非与运算
public void notServeInNaiveWaiter() ...{
System.out.println("--notServeInNaiveWaiter() executed!--");
}
@AfterReturning("target(com.baobaotao.Waiter) || "+
" target(com.baobaotao.Seller)") ③或运算
public void waiterOrSeller()...{
System.out.println("--waiterOrSeller() executed!--");
}
}
命名切点
在前面所举的例子中,切点直接声明在增强方法处,这种切点声明方式称为匿名切点,匿名切点只能在声明处使用。如果希望在其它地方重用一个切点,我们可以通过@Pointcut注解以及切面类方法对切点进行命名,以下是一个具体的实例:
代码清单 8 TestNamePointcut
@Pointcut("execution(* greetTo(..)))") ②通过注解方法greetTo()对该切点进行命名,方法可视域package com.baobaotao.aspectj.advanced;
import org.aspectj.lang.annotation.Pointcut;
public class TestNamePointcut ...{
@Pointcut("within(com.baobaotao.*)") ①通过注解方法inPackage()对该切点进行命名,方法可视域
修饰符为private,表明该命名切点只能在本切面类中使用。
private void inPackage()...{}
修饰符为protected,表明该命名切点可以在当前包中的切面
类、子切面类中中使用。
protected void greetTo(){}
@Pointcut("inPackage() and greetTo()") ③引用命名切点定义的切点,本切点也是命名切点,
它对应的可视域为public
public void inPkgGreetTo()...{}
}
我们在代码清单 8中定义了3个命名切点,命名切点的使用类方法作为切点的名称,此外方法的访问修饰符还控制了切点的可引用性,这种可引用性和类方法的可访问性相同,如private的切点只能在本类中引用,public的切点可以在任何类中引用。命名切点仅利用方法名及访问修饰符的信息,所以习惯上,方法的返回类型为void,并且方法体为空。我们可以通过下图更直观地了解命名切点的结构:

图 8 命名切点结构
在③处,inPkgGreetTo()的切点引用了同类中的greetTo()切点,而inPkgGreetTo()切点可以被任何类引用。你还可以扩展TestNamePointcut类,通过类的继承关系定义更多的切点。
命名切点定义好后,就可以在定义切面时通过名称引用切点,请看下面的实例:
package com.baobaotao.aspectj.advanced;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class TestAspect ...{
@Before("TestNamePointcut.inPkgGreetTo()") ①
public void pkgGreetTo()...{
System.out.println("--pkgGreetTo() executed!--");
}
@Before("!target(com.baobaotao.NaiveWaiter) && TestNamePointcut.inPkgGreetTo()") ②
public void pkgGreetToNotNaiveWaiter()...{
System.out.println("--pkgGreetToNotNaiveWaiter() executed!--");
}
}
在①处,我们引用了TestNamePointcut.inPkgGreetTo()切点,而在②处,我们在复合运算中使用了命名切点。