在上一篇博客初窥AspectJ中,我们提到AspectJ给java提供了三种新的结构,pointcut,advice以及inter-type declaration(ITD),而且我们通过一个简单的Demo介绍了如何使用pointcut和advice。而本文将介绍inter-type declaration是什么,可以做什么,最后同样会通过一个Demo来介绍如何使用。后文将主要用ITD来表示inter-type declaration。
本文中Demo的代码可以在github aspect-demo中找到。
inter-type declaration (ITD),翻译成中文是类型间声明。即使看到中文翻译,相信大家还是一头雾水,不知所云,所以我不是很喜欢对一些英文名字,尤其是技术名字进行生硬的翻译,这只会增加大家的理解负担。其实,换一种说法可能更好理解,member introduction(成员注入),其目的就是通过aspect的方式,在现有的类中注入一些新的成员变量或者成员方法。通过aspect,我们可以向一个类中注入如下成员:
成员变量(final或者非final)
方法
构造函数
除了往类里面添加内容,aspect还可以修改java中的interface(接口),实现在现有接口中注入:
方法的默认实现
非final的域
通过ITD注入的成员的访问修饰符可以是:
private: 通过private声明的私有成员属于目标类,但是呢,只对aspect脚本可见,而对目标类不可见;
public: 声明为public的成员对所有类和apsect都可见;
default package protected:这里的包可见性是相对于aspect所在的包,而不是相对于目标类所在的包。
inter-type declaration示例在编写aspect之前,先准备一个简单的java类:
package cc.databus.aspect.intertype; public class Point { private int x; private int y; public Point(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } }有了这个基础类,下面来看看如何通过aspect修改这个类实现的接口,成员变量以及成员方法。这里是我们的aspect代码:
package cc.databus.aspect.intertype; public aspect PointAspect { // creates a new interface named HasName private interface HasName{} // make class Ppint implements HashName declare parents: Point implements HasName; // make HasName has a field named name private String HasName.name; // make HasName has a method getName() and default implemented public String HasName.getName() { return name; } // make HasName has a method named setName and default public void HasName.setName(String name) { this.name = name; } // add a field named created to class Point // with default value 0 long Point.created = 0; // add a field named lastUpdated to class Point // with default value 0 private long Point.lastUpdated = 0; // add a private method setUpdated() private void Point.setUpdated() { this.lastUpdated = System.currentTimeMillis(); } // implement toString() for Point // include the fields added in the aspect file public String Point.toString() { return String.format( "Point: {name=%s, x=%d; y=%d, created=%d, updated=%d}", getName(), getX(), getY(), created, lastUpdated); } // pointcut the constructor, and set the value for created after() returning(Point p) : call(Point.new(..)) && !within(PointAspect) { System.out.println(thisJoinPointStaticPart); System.out.println("Set created"); p.created = System.currentTimeMillis(); } // define a pointcut for setX and setY pointcut update(Point p): target(p) && call(void Point.set*(..)); // make the lastUpdated updated every time // setX or setY invoked after(Point p): update(p) && !within(PointAspect) { System.out.println("set updated for Point due to " + thisJoinPointStaticPart); p.setUpdated(); } }在上面的aspect文件中,我们首先定义了一个接口,并且让Point类实现该接口,且给该新接口加了一个成员变量(name)并实现了对应的setter/getter:
// creates a new interface named HasName private interface HasName{} // make class Ppint implements HashName declare parents: Point implements HasName; // make HasName has a field named name private String HasName.name; // make HasName has a method getName() and default implemented public String HasName.getName() { return name; } // make HasName has a method named setName and default public void HasName.setName(String name) { this.name = name; }随后,我们给Point类加了两个成员变量,并实现了两个成员方法。其中,实现toString()接口的时候,我们把通过aspect注入的成员变量也都包含在结果里面:
// add a field named created to class Point // with default value 0 long Point.created = 0; // add a field named lastUpdated to class Point // with default value 0 private long Point.lastUpdated = 0; // add a private method setUpdated() private void Point.updated() { this.lastUpdated = System.currentTimeMillis(); } // implement toString() for Point // include the fields added in the aspect file public String Point.toString() { return String.format( "Point: {name=%s, x=%d; y=%d, created=%d, updated=%d}", getName(), getX(), getY(), created, lastUpdated); }