AspectJ——切入点语法(7)之this、target、args、if以及逻辑运算

更多的切入点语法

本节介绍AspectJ中更多的切入点语法,有很多在之前都用过,这里做一个总结。

0.捕获this引用的是特定类型对象的连接点

AspectJ提供了this原生切入点来捕获所有的连接点,这些连接点处的this引用的是一个特定的类型。

我们在Test13包下做测试,首先业务类Service如下:

package Test13;

public class Service {
    public int add(int a, int b) {
        return a + b;
    }

    public double square(double a) {
        return a * a;
    }

    public String upper(String string) {
        return string.toUpperCase();
    }
}

其包含三个方法。测试类Main如下:

package Test13;

public class Main {
    public static void main(String[] args) {
        Service service = new Service();
        System.out.println("service.add(1, 2) = " + service.add(1, 2));
        System.out.println("service.square(6) = " + service.square(6));
        System.out.println("service.upper(\"Gavin\") = " + service.upper("Gavin"));
    }
}

接着我们定义切面ThisAspect,如下:

package Test13;

public aspect ThisAspect {
    pointcut thisPointcut(Service service): this(service);

    before(Service service): thisPointcut(service){
        System.out.println(thisJoinPoint);
        System.out.println(thisJoinPoint.getSourceLocation());
        System.out.println(service);
    }
}

切面中,我们定义了thisPointcut(Service service)切入点,该切入点使用this(service)选择在源代码中this引用的是一个Service对象的连接点。在这里例子中,其实也就是所有在Service类中的连接点。运行结果如下:

这里写图片描述

通常情况下,this原生切入点和其他切入点配合使用,使在通知中可以使用当前this引用的对象。

1.使用target捕获目标对象是特定类型的连接点

this原生切入点类似,target原生切入点可以捕获所有目标对象是特定类型连接点。如在上例中,我们将切入点的this变成target

package Test13;

public aspect ThisAspect {
    pointcut thisPointcut(Service service): target(service);

    before(Service service): thisPointcut(service){
        System.out.println(thisJoinPoint);
        System.out.println(thisJoinPoint.getSourceLocation());
        System.out.println(service);
    }
}

此时,就会捕获所有目标对象是Service类对象的连接点,运行结果如下:

这里写图片描述

可以看到,多出来了Main类中的几个对Service类方法的调用连接点,因为这些连接点的目标对象是Service类的对象,所以都被捕获到。而其他的原本就在Service类中的连接点也依然被捕获到,它们的目标对象显然也是Service类对象。

target原生切入点通常也与其他切入点结合使用。

2.使用args捕获具有特定参数类型、参数次序和参数个数的连接点

使用args可以捕获具有特定参数类型、参数次序、参数个数的连接点。在args中也可以使用通配符,用以选择通配符指定的具有特定参数个数和次序的连接点。

我们在Test14包下做测试,业务类Service和测试类Main与上例一样,另外我们新建切面ArgsAspect,如下:

package Test14;

public aspect ArgsAspect {
    pointcut argsPointcut(): args(*, *);

    before(): argsPointcut(){
        System.out.println(thisJoinPoint);
        System.out.println(thisJoinPoint.getSourceLocation());
    }
}

我们使用args(*, *)选择具有两个参数的连接点。运行结果如下:

这里写图片描述

如果我们将args(*, *)换成args(*),那么就只会选择只有一个参数的通配符了:

package Test14;

public aspect ArgsAspect {
    pointcut argsPointcut(): args(*) && !within(ArgsAspect);

    before(): argsPointcut(){
        System.out.println(thisJoinPoint);
        System.out.println(thisJoinPoint.getSourceLocation());
    }
}

另外,还可以使用args(*, int),选择有两个参数,并且第二个参数是int类型的连接点;args(float, .., int)选择第一个参数是float类型,最后一个参数是int类型的,并且中间有任意个参数的连接点。

通常情况下,args通常和其他切入点一起使用,来获取连接点的参数,如下用法:

package Test14;

public aspect ArgsAspect {
    pointcut argsPointcut(int a, int b): call(* add(..)) && args(a, b);

    before(int a, int b): argsPointcut(a, b){
        System.out.println(thisJoinPoint);
        System.out.println(thisJoinPoint.getSourceLocation());
        System.out.println("第一个参数是:" + a);
        System.out.println("第二个参数是:" + b);
    }
}

运行结果是:

这里写图片描述

3.切入点的与或非运算

切入点之间可以进行与或非运算,分别对应符号&&||!

&&表示同时满足多个切入点表达式的切入点。关于&&的示例前面见过很多,这里不再赘述。

||的意义也很明显,它可以让切入点选择多个不同的连接点。比如,我们将上面的例子修改如下:

public aspect ArgsAspect {
    pointcut argsPointcut(): call(* add(..)) || call(* square(..));

    before(): argsPointcut(){
        System.out.println(thisJoinPoint);
        System.out.println(thisJoinPoint.getSourceLocation());
    }
}

call(* add(..)) || call(* square(..))表示捕获对add方法的调用和对square方法的调用,运行结果如下:

这里写图片描述

!运算前面也用到过,其作用是取反,比如!withincode(* add(..))排除在add方法中的连接点,!within(ArgsAspect)排除在切面ArgsAspect中的连接点。另外,非运算也可以用在某一个方法签名的字段前面, 比如我们继续将上例修改如下:

public aspect ArgsAspect {
    pointcut argsPointcut(): execution(public !static * *(..));

    before(): argsPointcut(){
        System.out.println(thisJoinPoint);
        System.out.println(thisJoinPoint.getSourceLocation());
    }
}

切入点execution(public !static * *(..))表示所有非静态方法的执行连接点,也就是捕获除了public static void main(String[] args)方法之外的其他所有方法的执行连接点,运行结果如下:

这里写图片描述

如果将!static改为static,那么此时的运行结果为:

这里写图片描述

两者的区别显而易见。

4.使用if来设置连接点上的运行时条件

if原生切入点可以设置运行时条件,只有在if判断为true的时候,才捕获该连接点。语法是:

pointcut [切入点名字](要获取的参数): if(Boolean expression);

其具有两个关键特征:

  1. if(Boolean expression)切入点评估在运行时提供的变量,得到关于连接点是否应该触发相应通知的truefalse结果。
  2. expression可以由多个逻辑元素组成,包括展示的连接点环境、静态变量、以及其他切入点声明。

我们在Test15包下做一个简单的测试(这个测试可能不太符合常理,不过演示出效果即可),首先新建People类:

package Test15;

public class People {
    private String name;
    private int age;

    public People(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void drive() {
        System.out.println(name + "不可以开车...");
    }
}

其中它有属性nameage,另外其具有drive()开车方法,我们规定只有年龄大于18岁才可以开车,并且默认地认为当前对象不可以开车。所以我们新建切面,来捕获对drive()方法的执行,并为其织入环绕通知:

package Test15;

public aspect DriveAspect {
    pointcut drivePointcut():execution(void People.drive())
                        && if((thisJoinPoint.getThis() instanceof People)
                        && ((People)thisJoinPoint.getThis()).getAge() > 18);

    void around():drivePointcut(){
        System.out.println(((People)thisJoinPoint.getThis()).getName() + "可以开车...");
    }
}

在切入点drivePointcut中,我们捕获了对drive()方法的执行,并且只有在当前this引用的People对象的年龄大于18的时候,我们才捕获该切入点,此时在为其织入的环绕通知中,我们改变原来程序的运行逻辑,输出可以开车

接着,新建测试类Main如下:

package Test15;

public class Main {
    public static void main(String[] args) {
        People people = new People("Gavin", 17);
        people.drive();
    }
}

首先,我们设置“Gavin”的年龄为17岁,这时的运行结果是:

这里写图片描述

接着,假如我们设置“Gavin”的年龄为19岁,这时的运行结果为:

这里写图片描述

两个结果与我们的预期完全一样,说明我们使用if定义的切入点完全正确。

猜你喜欢

转载自blog.csdn.net/ggGavin/article/details/80268846
今日推荐