AspectJ——切入点语法(3)之捕获类和对象构造上的连接点

捕获类和对象构造上的连接点

在Java中,一个类在实例化之前要经过三个步骤:装载、连接、初始化。装载即通过类型的完全限定名,产生一个代表该类型的二进制数据流,解析这个二进制数据流为方法区内的内部数据结构,并且创建一个表示该类型的java.lang.Class类的实例。连接即Java虚拟机为类变量分配内存,设置默认的初始值,并且解析变量。初始化主要完成对静态变量的初始化、静态块执行等工作,Java编译器会收集所有的类变量初始化语句和类型的静态初始化器,将这些放到一个特殊的方法中:<clinit>方法(这里不展开,有兴趣可以看《深入理解Java虚拟机》这本书)。

AspectJ提供了一些特殊的切入点来捕获类和对象的构造过程中的连接点。主要有:

  • 捕获构造方法的调用(用call(Signature)切入点,带有额外的new关键字作为签名的一部分)
  • 捕获构造方法的执行(用execution(Signature)切入点,带有额外的new关键字作为签名的一部分)
  • 捕获对象初始化(用initialization(Signature)切入点)
  • 捕获对象预先初始化(用preinitialization(Signature)切入点)
  • 捕获类的初始化(用staticinitialization(TypePattern)切入点)。

语法以及关键点

语法 描述 关键点
pointcut [切入点名字](<要捕获的参数>): call(<修饰符> 类名.new(参数列表)) 捕获构造方法的调用 (1)在把一个类实例化成一个对象时,具有new关键字的call(Signature)切入点会捕获连接点;
pointcut [切入点名字](<要捕获的参数>): execution(<修饰符> 类名.new(参数列表)) 捕获构造方法的执行 (1)在执行类的构造函数时,具有new关键字的execution(Signature)切入点会捕获连接点
pointcut [切入点名字](<要捕获的参数>): initialization(<修饰符> 类名.new(参数列表)) 捕获对象初始化 (1)initialization(Signature)切入点必须包含new关键字;(2)initialization(Signature)切入点捕获连接点发生在任何超类的初始化之后,以及从构造方法返回之前;(3)Signature必须解析成特定类的构造方法,而不是简单的方法;(4)initialization(Signature)切入点提供了编译时检查,用于检查构造方法是否正在被引用;(5)由于AspectJ编译器中的限制,当与around()通知关联时,不能使用initialization(Signature)切入点
pointcut [切入点名字](<要捕获的参数>): preinitialization(<修饰符> 类名.new(参数列表)) 捕获对象的预先初始化 (1)preinitialization(Signature)切入点必须包含new关键字;(2)preinitialization(Signature)切入点捕获连接点发生在进入构造方法之后,以及调用任何超类构造方法之前。(3)Signature必须解析成一个构造方法。(4)preinitialization(Signature)切入点提供了编译时检查,用于检查构造方法是否正在被引用;(5)由于AspectJ编译器中的限制,当与around()通知关联时,不能使用preinitialization(Signature)切入点
pointcut [切入点名字](<要捕获的参数>): staticinitialization(类名) 捕获类的初始化 (1)对staticinitialization(TypePattern)切入点使用的环境有一些限制,首先,没有父对象触发类的初始化,所以没有this引用;另外,也不涉及目标对象,故没有target目标引用;(2)TypePattern可以包含通配符,用于选择一系列不同的类。

示例

我们在Test6包下做简单的测试,首先创建业务超类SuperService,业务类Service继承自SuperService。测试类为Main,最后创建切面ConstructorAspect

这里写图片描述

首先,SuperService如下:

package Test6;

public class SuperService {
    private String name;

    public SuperService(String name) {
        this.name = name;
    }
}

SuperService只提供了一个name属性,和一个构造方法。Service类继承自SuperService,如下:

package Test6;

public class Service extends SuperService{
    private String name;
    private int age;

    static {
        System.out.println("Static 静态代码块...");
    }

    public Service(String name) {
        super(name);
        this.name = name;
    }

    public Service(int age) {
        super("Gavin");
        this.age = age;
    }

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

    public void test(int a, float b) {
        System.out.println("a + b = " + (a + b));
    }

    @Override
    public String toString() {
        return "Service{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

Service类中有一个静态代码块,因为静态代码块是在类初始化的过程中执行的,所以我们可以借此验证staticinitialization(TypePattern)切入点的作用。

主方法类Main如下:

package Test6;

public class Main {
    public static void main(String[] args) {
        Service service = new Service("Gavin");
        service.test(1, 2.5F);
    }
}

最后,我们创建ConstructorAspect切面:

package Test6;

public aspect ConstructorAspect {
    pointcut constructorCallPointcut(): call(SuperService+.new(..));

    pointcut constructorExecutionPointcut():execution(SuperService+.new(..));

    pointcut initializationPointcut(): initialization(SuperService+.new(..));

    pointcut preInitializationPointcut(): preinitialization(SuperService+.new(..));

    pointcut staticInitializationPointcut(): staticinitialization(SuperService+);

    before(): constructorCallPointcut(){
        System.out.println();
        System.out.println("调用Service类构造函数之前...");
        System.out.println("Signature: " + thisJoinPoint.getSignature());
        System.out.println("Source Line: " + thisJoinPoint.getSourceLocation());
    }

    before(): constructorExecutionPointcut(){
        System.out.println();
        System.out.println("执行" + thisJoinPoint.getThis().getClass() + "类的构造函数之前...");
        System.out.println("Signature: " + thisJoinPoint.getSignature());
        System.out.println("Source Line: " + thisJoinPoint.getSourceLocation());
    }

    before(): initializationPointcut(){
        System.out.println();
        System.out.println("initializationPointcut在这里...");
        System.out.println("Signature: " + thisJoinPoint.getSignature());
        System.out.println("Source Line: " + thisJoinPoint.getSourceLocation());
    }

    before(): preInitializationPointcut(){
        System.out.println();
        System.out.println("preInitializationPointcut在这里...");
        System.out.println("Signature: " + thisJoinPoint.getSignature());
        System.out.println("Source Line: " + thisJoinPoint.getSourceLocation());
    }

    after(): staticInitializationPointcut(){
        System.out.println();
        System.out.println("staticInitializationPointcut在这里...");
        System.out.println("Signature: " + thisJoinPoint.getSignature());
        System.out.println("Source Line: " + thisJoinPoint.getSourceLocation());
    }
}

在该切面中,我们创建了上文中介绍的几种切入点,SuperService+.new(..)表示SuperServcie类以及其子类的构造方法,且不考虑构造方法的参数,这里的加号+表示继承关系。

运行结果如下:

这里写图片描述

结果分析:

  1. 首先最先出现的动作是调用Service类的构造方法,所以该连接点最先被捕获。
  2. 接下来是类的初始化动作,首先初始化超类SuperService,再初始化Service。由于类的初始化切入点staticInitializationPointcut织入的是after()后置通知,所以Static 静态代码块...这句话在Service类初始化切入点通知之前被执行。
  3. 由于对象的预先初始化preinitialization(Signature)切入点捕获连接点发生在进入构造方法之后,以及调用任何超类构造方法之前。所以,从运行结果也可以看出来,先捕获了Service类对象的预先初始化连接点,紧接着,在子类构造方法中要调用父类构造方法,所以进入了父类SuperService的构造方法,故捕获了SuperService类对象的预先初始化连接点。
  4. 接下来,捕获SuperService的对象初始化连接点(initializationPointcut),该连接点是在构造方法执行(constructorExecutionPointcut)之前;
  5. 父类构造方法执行完成之后,返回子类构造方法,所以接连捕获了Service类的对象初始化连接点(initializationPointcut),以及构造方法执行连接点(constructorExecutionPointcut)。

猜你喜欢

转载自blog.csdn.net/gggavin/article/details/80230353