跟我学AspectJ(三)

来源:互联网 发布:理性谈恋爱知乎 编辑:程序博客网 时间:2024/04/28 01:34
引语
       在本系列的前一章中,我们简要的说明了AspectJ语言的总揽。为了理解AspectJ的语法和语义,你应该阅读本章。这一部分包括了前述的一些材料,但是将更加完整和更多的讨论细节。文章将由一个具体方面的例子开始,这个方面包括了一个切点,一个类型间声明和两个通知,这个例子将给我们一些讨论的话题。
分析方面(The Anatomy of an Aspect)
       首先给出我们定义的方面。
1 aspect FaultHandler {
 2
 3   private boolean Server.disabled = false;
 4
 5   private void reportFault() {
 6     System.out.println("Failure! Please fix it.");
 7   }
 8
 9   public static void fixServer(Server s) {
10     s.disabled = false;
11   }
12
13   pointcut services(Server s): target(s) && call(public * *(..));
14
15   before(Server s): services(s) {
16     if (s.disabled) throw new DisabledException();
17   }
18
19   after(Server s) throwing (FaultException e): services(s) {
20     s.disabled = true;
21     reportFault();
22   }
23 }
FaultHandler包括一个在Server上的类型间字段声明(第3行),两个方法(5-7行和9-11),切点定义(13行),两个通知(15-17行和19-22行)。这些覆盖了方面能够包括的基本信息。通常来说,方面包括其他程序实体、不同的变量和方法、切点定义、类型间声明和通知(可能有beforeafteraround)。文章的余下部分将逐一讨论这些横切相关的构造。
 
    切点(Pointcuts)
AspectJ的切点定义为切点取名。切点自己捕捉连接点集合,例如,程序执行中的赶兴趣的点集合。这些连接点可以是方法或者构造子的调用或执行,异常处理,字段的赋值和读取等等。举个例子,在13行的切点定义:
pointcut services(Server s): target(s) && call(public * *(..))
这个切点,被命名为services,捕捉当Server对象的公共方法被调用时程序执行过程中的连接点。它同样允许使用services切点的任何人访问那些方法被调用的Server对象。FaultHandler方面的这个切点背后的思想是指错误处理相关的行为必须由公共方法的调用来触发。例如,server可能因为某些错误不能处理请求。因此,对那些方法的调用是该方面感兴趣的事件,当这些事件发生时,对应的错误相关的事情也将发生。
事件发生时的部分上下文由切点的参数暴露。在这个例子中,就是指Server类型的对象。这个参数在切点声明的右边被使用,以便指明哪个事件与切点相关。在例子中,services切点包括两部分,它是由捕捉那些以Server为目标对象的操作(target(S))的连接点,与那些捕捉call的连接点(call(..))组合起来(&&,意为逻辑与and)形成的切点。调用连接点(calls)通过方法签名描述,在此例中,使用了几个通配符表达。在返回类型的位置是(*),在方法名的位置是(*)而在参数列表位置是(..);只指定了一个具体信息,就是public
切点捕捉程序中的大量连接点。但是它们仅仅捕捉几种连接点。这些种类的连接点代表了Java语言中的一些重要概念。这里是对于这些概念的不完整列表:方法调用、方法执行、异常处理、实例化、构造子执行以及字段的访问。每一种连接点都由特定的切点捕捉,你将在本文的其他部分学到它们。
 
   通知(Advice)
       一个通知由切点和通知代码组成,它定义了在切点捕捉到的连接点处运行的逻辑。例如,15-17行的通知中的代码if (s.disabled) throw new DisabledException();Server的实例调用它的公共方法时执行。19-22行定义的另一个通知在同一个切点(services)处执行。
{
 s.disabled = true;
 reportFault();
}
当时第二个通知只有在程序抛出FaultException时才执行。有三类的after通知:分别基于方法正常结束、方法异常结束和方法一任何方式结束。
 
连接点和切点(Join Points and Pointcuts)
考虑下面的Java
class Point {
    private int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }
 
    void setX(int x) { this.x = x; }
    void setY(int y) { this.y = y; }
 
    int getX() { return x; }
    int getY() { return y; }
}
为了能够理解AspectJ的连接点和切点概念,让我们回顾以下Java语言的一些基本原理。考虑类Point中的方法声明       void setX(int x) { this.x = x; } 这个程序片段说明当Point类实例调用名为 setX有一个整型参数的方法时,程序执行方法体{ this.x=x;}。与此类似的是,类的构造子表明如果当Point类使用两个整型参数实例化时,构造子体内的{this.x=x;this.y=y;}将被执行。用一句话总结就是:当一些事发生时,就有一些东西被执行。在面向对象程序中,有一些种类的“发生的事”是由语言本身所决定。我们把这些称为Java的连接点。连接点有一些像方法调用、方法执行、对象实例化、构造子执行、字段引用以及异常处理等组成。
而切点就是用来捕捉这些连接点的结构,例如,下面的切点
pointcut setter(): target(Point) &&
                   (call(void setX(int)) ||
                    call(void setY(int)));
捕捉对于Point实例上setXint)或setYint)的每一个方法调用。看看另外一个例子
pointcut ioHandler(): within(MyClass) && handler(IOException);
这个切点捕捉类MyClass内异常处理代码执行时的每个连接点。
切点定义包括由冒号分割的两部分。左边包括切点的名称和切点的参数(例如事件发生时的数据)。右边则包括切点本身。
 
一些切点的例子
下面的切点是特定方法执行时起作用
       execution(void Point.setX(int))
进行方法调用时则使用
       call(void Point.setX(int))
异常处理执行时的切点定义如下
       handler(ArrayOutOfBoundsException)
当前正在使用SomeType类型的对象
this(SomeType)
SomeType类型对象为目标对象时
target(SomeType)
如果连接点处在Test的无参数main函数调用流程中
cflow(call(void Test.main())
切点还可以使用或(“||”)以及与(“and”)和非(“!”)组合。
·可以使用通配符。因此
1.Execution(* *(..))
2.Call(* set(..))
代表(1)不考虑参数和返回值的任何方法执行(2)对于任何参数和返回值的方法名为set方法的调用。
·可以基于类型选择元素,例如
1.Execution(int *())
2.Call(* setY(long))
3.Call(* Point.setY(int))
4.Call(*.new(int,int))
代表(1)返回值是int型的任何无参数方法执行;(2)任何返回类型且参数为long类型的名为setY方法的调用;(3)任意Point对象的有一个int类型setY方法的调用,忽略返回类型;(4)对于任何类的构造子的调用,只要该构造子有两个int类型的参数。
·如何组合切点,例如
1.Target(Point) && call(int *())
2.Call(* *(..)) && (within(Line) || within(Point))
3.Within(*) && execution(*.new(int))
4.!this(Point) && call(int *(..))
代表(1Point实例上返回类型为int的任意无参数方法调用;(2LinePoint定义及其实例产生的任意方法调用;(3)任意带一个int类型参数的构造子调用;(4)任意返回类型为int类型的方法调用只要当前执行类实例不是Point类型。
·选择有特定修饰的方法或构造子
1.Call(public * *(..))
2.Execution(!static * *(..))
3.Execution(public !static * *(..))
代表(1)任意公共方法的调用;(2)任意非静态方法的执行;(3)任意公共非静态方法的执行。
·切点还能够处理接口。例如给定接口
       interface MyInterface { … }
       切点call(* MyInterface.*(..))捕捉MyInterface定义的方法以及超类型定义的方法调用。
 
切点合成
切点可以使用操作符与(&&)、或(||)和非(!)。这样可以使用简单的原始切点来创建强大功能的切点。当使用原始切点cflowcflowbelow进行组合时,可能会有些迷糊。例如,cflowp)捕捉p流程内(包括P在内)的所有连接点,可以使用图形表示
 P ---------------------
    /
     / cflow of P
      /
那么cflow(P) && cflow(Q)捕捉的是什么呢?它捕捉同时处于PQ流程中的连接点。
             P ---------------------
             /
               / cflow of P
                 /
                  /
                  /
 Q -------------/-------
    /                 /
     / cflow of Q  /  cflow(P) && cflow(Q)
      /                    /
注意PQ可能没有任何公共的连接点,但是它们的程序流程中可能有公共的连接点。
Cflow(P && Q)又是什么意思呢?它的意思是被PQ共同捕捉的连接点的流程。
   P && Q -------------------
          /
           /  cflow of (P && Q)
            /
如果PQ没有捕捉到公共的连接点,那么在(P&&Q)的程序流程中不可能有任何连接点。下面代码表明上述意思
public class Test {
    public static void main(String[] args) {
        foo();
    }
    static void foo() {
        goo();
    }
    static void goo() {
        System.out.println("hi");
    }
}
 
aspect A {
    pointcut fooPC(): execution(void Test.foo());
    pointcut gooPC(): execution(void Test.goo());
    pointcut printPC(): call(void java.io.PrintStream.println(String));
 
    before(): cflow(fooPC()) && cflow(gooPC()) && printPC() {
        System.out.println("should occur");
    }
 
    before(): cflow(fooPC() && gooPC()) && printPC() {
        System.out.println("should not occur");
    }
}
切点参数
考虑下述代码
 pointcut setter():
target(Point) && (call(void setX(int)) || call(void setY(int)));
这个切点捕捉以Point实例为目标的每个setX(int)setY(int)方法的调用。切点名为setters并且在左边没有任何参数。空的参数列意味着切点不会暴露任何连接点的上下文信息。但是考虑另一版本的切点定义
 pointcut setter(Point p):
target(p) && (call(void setX(int)) || call(void setY(int)));
它的功能与前述的切点相同,但是这里的切点包括一个参数类型为Point。这就意味着任何使用这个切点的通知都可以访问被切点捕捉连接点中的Point实例。
现在看看又一版本的setters切点
pointcut setter(Point p, int newval): target(p) && args(newval)
&& (call(void setX(int)) || call(void setY(int)));
这里切点暴露了一个Point对象和一个int值。在切点定义的右边,我们发现Point对象是目标对象,而且int值是被调用方法的参数。
       切点参数的使用有很大的伸缩性。最重要的规则是所有的切点参数必须被绑定在每个切点捕捉的连接点上。因此,下面的例子就会参数编译错误。
 pointcut badPointcut(Point p1, Point p2):
      (target(p1) && call(void setX(int))) ||
(target(p2) && call(void setY(int)));
因为p1仅当setX调用时被绑定,而       p2只在setY调用时被绑定,但是切点却捕捉所有的这些连接点并且试图同时绑定p1p2
 
一个例子:HandleLiveness
这个例子包括两个对象类,一个异常类和一个方面。Handle对象仅仅是代理Partner对象的非静态公共方法。方面HandleLiveness确保在代理之前Partner存在并且可用,否则抛出一个异常。
 class Handle {
    Partner partner = new Partner();
    public void foo() { partner.foo(); }
    public void bar(int x) { partner.bar(x); }
    public static void main(String[] args) {
      Handle h1 = new Handle();
      h1.foo();
      h1.bar(2);
    }
 }
  class Partner {
    boolean isAlive() { return true; }
    void foo() { System.out.println("foo"); }
    void bar(int x) { System.out.println("bar " + x); }
 }
  aspect HandleLiveness {
    before(Handle handle): target(handle) && call(public * *(..)) {
      if ( handle.partner == null || !handle.partner.isAlive() ) {
        throw new DeadPartnerException();
      }
    }
 }
   class DeadPartnerException extends RuntimeException {}
 
原创粉丝点击