1 切点复合运算
支持在切点定义中加入以下运算符进行复合运算:
2 切点命名
一般情况下,切点是直接声明在需要增强方法处,这种切点的声明方式称为匿名切点,匿名切点只能在声明处被使用 。 如果希望在其它地方可以重用这个切点,我们可以通过 @Pointcut 注解及切面类方法来命名它。
public class NamePointcut { /** * 切点被命名为 method1,且该切点只能在本类中使用 */ @Pointcut("within(net.deniro.spring4.aspectj.*)") private void method1() { } /** * 切点被命名为 method2,且该切点可以在本类或子孙类中使用 */ @Pointcut("within(net.deniro.spring4.aspectj.*)") protected void method2() { } /** * 切点被命名为 method3,且该切点可以在任何类中使用 * 这里还使用了复合运算 */ @Pointcut("method1() && method2()") public void method3() { }}
命名切点的结构如下:
切点可访问性修饰符与类可访问性修饰符的功能是相同的,它可以决定定义的切点可以在哪些类中可使用。
因为命名切点仅利用方法名及访问修饰符的信息,所以我们一般定义方法的返回类型为 void ,并且方法体为空 。
定义好切点后,就可以在切面类中引用啦:
@Aspectpublic class NamePointcutAspect { @After("NamePointcut.method2()") public void aspectMethod1() { } /** * 这里使用了复合运算 */ @After("NamePointcut.method2() && NamePointcut.method3()") public void aspectMethod2() { }}
3 织入顺序
一个连接点可以同时匹配多个切点,而切点所对应的增强在连接点上织入顺序的规则是这样的:
1.如果在同一个切面类中声明的增强,则按照增强在切面类中定义的顺序进行织入;
- 如果增强位于不同的切面类中,并且这些切面类都实现了org.springframework.core.Ordered 接口,则由 Ordered 方法的顺序号决定(顺序号小的先织入);
- 如果增强位于不同的切面类中,但这些切面类没有实现org.springframework.core.Ordered 接口,织入的顺序是不确定的 。
假设有两个切面类 A 与 B,它们都实现了 Ordered 接口,A 的顺序号为 1,B 的顺序号为 2,切面类 A 与 B 都定义了 3 个增强,那么同时匹配这 6 个增强的织入顺序如下图所示:
4 获取连接点信息
4.1 JoinPoint
org.aspectj.lang.JoinPoint 接口表示目标类连接点对象,它定义这些主要方法。
4.2 ProceedingJoinPoint
org.aspectj.lang.ProceedingJoinPoint 继承了 JoinPoint 接口,它新增了两个方法(它们用于执行连接点方法)。
4.3 示例
Cook 接口:
public interface Cook { /** * 制作食品 */ void make(); /** * 制作 * * @param name 食品名称 */ void make(String name);}
CookA 类:
public class CookA implements Cook { public void make() { System.out.println("制作食品"); } public void make(String name) { System.out.println("制作" + name); }}
在切面类中访问连接点信息:
@Aspectpublic class JoinPointAspect { @Around("within(net.deniro.spring4.aspectj.CookA)") public void test(ProceedingJoinPoint pjp) throws Throwable { System.out.println("---------获取连接点对象【开始】---------"); System.out.println("参数:" + pjp.getArgs()[0]); System.out.println("签名对象:" + pjp.getTarget().getClass()); //执行目标对象方法 pjp.proceed(); System.out.println("---------获取连接点对象【结束】---------"); }}
Spring bean 配置:
<?xml version="1.0" encoding="UTF-8"?>
输出结果:
---------获取连接点对象【开始】---------
参数:寿司
签名对象:class net.deniro.spring4.aspectj.CookA
制作寿司
---------获取连接点对象【结束】---------
5 绑定连接点的方法入参
args()、this()、target()、@args()、@within()、@target() 和 @annotation() 这些切点函数除可以指定类名外,还可以指定参数名,将目标对象连接点上的方法入参绑定到增强的方法中 。 其中 args() 用于绑定连接点方法的入参, @annotation() 用于绑定连接点方法的注解对象,而 @args() 用于绑定连接点方法入参的注解。
CookC 类:
public class CookC implements Cook { public void make() { System.out.println("制作食品"); } public void make(String name) { System.out.println("制作" + name); } public void make(String name, int num) { System.out.println("制作" + name + " " + num + " 个"); }}
切面类:
@Aspectpublic class ParamsAspect { @Before("target(net.deniro.spring4.aspectj.CookC) && args(name,num,..)") public void test(String name,int num) { System.out.println("----------绑定连接点入参【开始】----------"); System.out.println("name:" + name); System.out.println("num:" + num); System.out.println("----------绑定连接点入参【结束】----------"); }}
- 这里的连接点表达式 args(name,num,..) 会先找到 name 与 num 的类型,从而生成真正的表达式 args(String,int,..)。
- 增强方法可以通过 name 与 num 得到连接点的方法入参。
切点匹配和参数绑定的过程是这样的:
- args()会根据参数名称在增强方法中查到名称相同的入参并获得对应参数的类型,这样就得到了匹配连接点方法的入参类型 。
- 连接点方法入参类型所在的位置由参数名在 args() 函数中声明的位置决定 。
上述示例中的匹配过程如下:
Spring 配置:
注意: 这里必须通过 来启用 CGLib 动态代理,这是因为 CookC 的 public void make(String name, int num) 是该类独有的方法(非接口定义的方法),所以必须使用 CGLib 生成子类的代理方法 。
单元测试:
ApplicationContext context = new ClassPathXmlApplicationContext("spring-beans.xml");CookC cookC = (CookC) context.getBean("cookC");cookC.make("寿司