捕获类和对象构造上的连接点
在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
类以及其子类的构造方法,且不考虑构造方法的参数,这里的加号+
表示继承关系。
运行结果如下:
结果分析:
- 首先最先出现的动作是调用
Service
类的构造方法,所以该连接点最先被捕获。 - 接下来是类的初始化动作,首先初始化超类
SuperService
,再初始化Service
。由于类的初始化切入点staticInitializationPointcut
织入的是after()
后置通知,所以Static 静态代码块...
这句话在Service
类初始化切入点通知之前被执行。 - 由于对象的预先初始化
preinitialization(Signature)
切入点捕获连接点发生在进入构造方法之后,以及调用任何超类构造方法之前。所以,从运行结果也可以看出来,先捕获了Service
类对象的预先初始化连接点,紧接着,在子类构造方法中要调用父类构造方法,所以进入了父类SuperService
的构造方法,故捕获了SuperService
类对象的预先初始化连接点。 - 接下来,捕获
SuperService
的对象初始化连接点(initializationPointcut
),该连接点是在构造方法执行(constructorExecutionPointcut
)之前; - 父类构造方法执行完成之后,返回子类构造方法,所以接连捕获了
Service
类的对象初始化连接点(initializationPointcut
),以及构造方法执行连接点(constructorExecutionPointcut
)。