第二章 AspectJ语言
通知(Advice)
通知定义几个方面的实现,以便于在特定的程序运行段执行。这些特定段可以使用命名的切点给出也可以使用匿名切点。下面是一个使用命名切点的通知的例子:
pointcut setter(Point p1, int newval): target(p1) && args(newval)
(call(void setX(int) ||
call(void setY(int)));
before(Point p1, int newval): setter(p1, newval) {
System.out.println("About to set something in " + p1 +
" to the new value " + newval);
}
使用匿名切点也可以实现同样的通知,如下
before(Point p1, int newval): target(p1) && args(newval)
(call(void setX(int)) ||
call(void setY(int))) {
System.out.println("About to set something in " + p1 +
" to the new value " + newval);
}
下面是一些不同的通知
这个before通知在匿名切点捕捉到连接点之前运行
before(Point p, int x): target(p) && args(x) && call(void setX(int)) {
if (!p.assertX(x)) return;
}
这个after通知在匿名切点捕捉各个连接点之后运行,无论程序是正常返回还是抛出异常。
after(Point p, int x): target(p) && args(x) && call(void setX(int)) {
if (!p.assertX(x)) throw new PostConditionViolation();
}
下面的after通知在匿名切点捕捉到连接点后且仅仅在程序正常返回的情况下在运行。
after(Point p) returning(int x): target(p) && call(int getX()) {
System.out.println("Returning int value " + x + " for p = " + p);
}
下面的after通知在匿名切点捕捉到连接点后且程序抛处异常的情况下运行。
after() throwing(Exception e): target(Point) && call(void setX(int)) {
System.out.println(e);
}
around通知可以取代被捕捉的连接点而执行它自己定义的逻辑,程序原有的逻辑可以通过调用一个特定的方法proceed执行。
void around(Point p, int x): target(p)
&& args(x)
&& call(void setX(int)) {
if (p.assertX(x)) proceed(p, x);
p.releaseResources();
}
类型间声明(Inter-type declarations)
方面能够声明属于其他类型的成员(字段、方法和构造子)。这些被称为类型间成员。方面也能够声明实现新接口或扩展一个新类的其他类型。这里有一些这样的声明的例子。
下述声明每个Server对象有一个名为disabled的boolean类型字段,其初始值为false:
private boolean Server.disabled=false;
这里声明了一个私有的字段,只有这个方面能够访问这个字段。就算是Server对象本身有另一个私有字段disabled(在Server内或其他方面里定义)也不会产生名字冲突,因为对于disabled引用没有二义性。
下面代码段声明每个Point对象有一个名为getX的方法,它返回各自x变量的值:
public int Point.getX(){ return this.x;}
在方法内部,this就是当前执行的Point对象。因为方法声明为公共方法,所以任何代码都能调用它,但是如果有另一个Point.getX()声明,那么就会产生编译期的冲突。
下面的公有声明定义Point对象的一个构造函数,它有两个整型参数:
public Point.new(int x,int y){ this.x=x;this.y=y; }
下面是一个公有字段声明:
public int Point.x=0;
它为Point对象声明了一个x公有字段并初始化为0,因为字段是公有的,在任何地方都可以访问它,所以如果还有另一个x字段,则会产生冲突。
下面声明了Point类实现的接口Comparable
declare parents : Point implement Comparable;
当然,除非Point类实现了接口的方法,否则会有错误。
下面则为Point类声明了其扩展的类GeometricObject
declare parents : Point extends GeometricObject;
一个方面可以有多个类型间声明。例如下面的声明。
Public String Point.name;
Public void Point.setName(String name){ this.name=name; }
类型间成员仅仅能够有一个目标类型,但是通常你可能想要在多个类型上声明相同的成员。这可以通过联合使用类型间成员和一个私有接口实现。
aspect A {
private interface HasName {}
declare parents: (Point || Line || Square) implements HasName;
private String HasName.name;
public String HasName.getName() { return name; }
}
这里声明了一个HasName接口,并声明Point、Line或Square都实现这个接口。而且为接口声明了私有字段name和公有方法getName。
类型间变量的作用域
AspectJ允许私有、包保护(缺省)以及公有的类型间声明。私有意味着与aspect有私有关系,而与目标对象无关(即目标对象不知道变量或方法的存在)。因此,如果一个方面作出一个字段的私有类型间声明
Private int Foo.x;
那么方面中的代码可以访问Foo的x字段,其他类或方面都不行。类似地,如果一个方面作出一个包保护类型间声明
int Foo.x;
那么在包中的任何代码都可以访问它,包外的代码无权访问。
举例:PointAssertions
这个例子包括一个类和一个方面。方面为Point声明了私有的assertion方法assertX和assertY。利用这两个断言方法方面对setX和setY方法的调用提供了保护。断言方法声明为私有是因为没有其他地方需要用到它们,只有方面内部可以使用这些方法。
class Point {
int x, y;
public void setX(int x) { this.x = x; }
public void setY(int y) { this.y = y; }
public static void main(String[] args) {
Point p = new Point();
p.setX(3);
p.setY(333);//非法的Y值
}
}
aspect PointAssertions {
//如果X或Y的值不在0到100之间,则视为非法。
private boolean Point.assertX(int x) {
return (x <= 100 && x >= 0);
}
private boolean Point.assertY(int y) {
return (y <= 100 && y >= 0);
}
before(Point p, int x): target(p) && args(x) && call(void setX(int)) {
if (!p.assertX(x)) { //若非法输入X,则输出提示信息
System.out.println("Illegal value for x"); return;
}
}
before(Point p, int y): target(p) && args(y) && call(void setY(int)) {
if (!p.assertY(y)) { //若非法输入Y,则输出提示信息
System.out.println("Illegal value for y"); return;
}
}
}
thisJoinPoint
AspectJ提供了一个特别的引用变量,thisJoinPoint,它包含了当前连接点处的相关信息并可以被通知使用。ThisJoinPoint变量仅仅可以在通知环境中被使用,就象this仅能用于非静态方法和构造函数环境中一样。在通知中,thisJoinPoint是org.aspectj.lang.JoinPoint类型的变量。使用它的一个简单作用是直接输出它。和其他Java对象一样,thisJoinPoint有一个toString()方法简化了格式化的输出:
class TraceNonStaticMethods {
before(Point p): target(p) && call(* *(..)) {
System.out.println("Entering " + thisJoinPoint + " in " + p);
}
}
thisJoinPoint可以被用来访问静态和动态信息,比如说参数等:
thisJoinPoint.getArgs();
另外,它持有一个包括所有静态信息的对象,可以通过以下方法得到该对象的引用:
thisJoinPoint.getStaticPart();
如果你仅需要关于连接点处的静态信息,你可能访问连接点的静态部分直接使用变量thisJoinPointStaticPart。使用它将避免运行时直接使用thisJoinPoint创建连接点对象。
通常情况下
thisJoinPointStaticPart == thisJoinPoint.getStaticPart()
thisJoinPoint.getKind() == thisJoinPointStaticPart.getKind()
thisJoinPoint.getSignature() == thisJoinPointStaticPart.getSignature()
thisJoinPoint.getSourceLocation() == thisJoinPointStaticPart.getSourceLocation()
还有一个相关变量:thisEnclosingJoinPointStaticPart。它与thisJoinPointStaticPart类似,使用它可以打印调用者的位置,例如
before() : execution (* *(..)) {
System.err.println(thisEnclosingJoinPointStaticPart.getSourceLocation())
}
导读
本系列下一章将使用AspectJ实现一些具体的例子,以便读者可以加强对AspectJ的理解并熟悉各种语法。
更多信息
如果需要转贴请写名作者和出处。