跟我学AspectJ(五)

原创 2003年10月27日 02:12:00

第三章         AspectJ实例<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

 

使用方面的Tracing程序

       写一个具有跟踪能力的类是很简单的事情:一组方法,一个控制其开或关的布尔变量,一种可选的输出流,可能还有一些格式化输出能力。这些都是Trace类需要的东西。当然,如果程序需要的话,Trace类也可以实现的十分的复杂。开发这样的程序只是一方面,更重要的是如何在合适的时候调用它。在大型系统开发过程中,跟踪程序往往影响效率,而且在正式版本中去除这些功能十分麻烦,需要修改任何包含跟踪代码的源码。出于这些原因,开发人员常常使用脚本程序以便向源码中添加或删除跟踪代码。

       AspectJ可以更加方便的实现跟踪功能并克服这些缺点。Tracing可以看作是面向整个系统的关注点,因此,Tracing方面可以完全独立在系统之外并且在不影响系统基本功能的情况下嵌入系统。

 

应用实例

整个例子只有四个类。应用是关于Shape的。TwoShape类是Shape类等级的基类。

public abstract class TwoDShape {

    protected double x, y;

    protected TwoDShape(double x, double y) {

        this.x = x; this.y = y;

    }

    public double getX() { return x; }

    public double getY() { return y; }

    public double distance(TwoDShape s) {

        double dx = Math.abs(s.getX() - x);

        double dy = Math.abs(s.getY() - y);

        return Math.sqrt(dx*dx + dy*dy);

    }

    public abstract double perimeter();

    public abstract double area();

    public String toString() {

        return (" @ (" + String.valueOf(x) + ", " + String.valueOf(y) + ") ");

    }

}

TwoShape类有两个子类,CircleSquare  

public class Circle extends TwoDShape {

    protected double r;

    public Circle(double x, double y, double r) {

        super(x, y); this.r = r;

    }

    public Circle(double x, double y) { this(  x,   y, 1.0); }

    public Circle(double r)           { this(0.0, 0.0,   r); }

    public Circle()                   { this(0.0, 0.0, 1.0); }

    public double perimeter() {

        return 2 * Math.PI * r;

    }

    public double area() {

        return Math.PI * r*r;

    }

    public String toString() {

        return ("Circle radius = " + String.valueOf(r) + super.toString());

    }

}

public class Square extends TwoDShape {

    protected double s;    // side

    public Square(double x, double y, double s) {

        super(x, y); this.s = s;

    }

    public Square(double x, double y) { this(  x,   y, 1.0); }

    public Square(double s)           { this(0.0, 0.0,   s); }

    public Square()                   { this(0.0, 0.0, 1.0); }

    public double perimeter() {

        return 4 * s;

    }

    public double area() {

        return s*s;

    }

    public String toString() {

        return ("Square side = " + String.valueOf(s) + super.toString());

    }

}

 

Tracing版本一

首先我们直接实现一个Trace类并不使用方面。公共接口Trace.java

public class Trace {

    public static int TRACELEVEL = 0;

    public static void initStream(PrintStream s) {...}

    public static void traceEntry(String str) {...}

    public static void traceExit(String str) {...}

}

如果我们没有AspectJ,我们需要在所有需要跟踪的方法或构造子中直接调用traceEntrytraceExit方法并且初试化TRACELEVEL和输出流。以上面的例子来说,如果我们要跟踪所有的方法调用(包括构造子)则需要40次的方法调用并且还要时刻注意没有漏掉什么方法,但是使用方面我们可以一致而可靠的完成。TraceMyClasses.java

aspect TraceMyClasses {

    pointcut myClass(): within(TwoDShape) || within(Circle) || within(Square);

    pointcut myConstructor(): myClass() && execution(new(..));

    pointcut myMethod(): myClass() && execution(* *(..));

 

    before (): myConstructor() {

        Trace.traceEntry("" + thisJoinPointStaticPart.getSignature());

    }

    after(): myConstructor() {

        Trace.traceExit("" + thisJoinPointStaticPart.getSignature());

    }

 

    before (): myMethod() {

        Trace.traceEntry("" + thisJoinPointStaticPart.getSignature());

    }

    after(): myMethod() {

        Trace.traceExit("" + thisJoinPointStaticPart.getSignature());

    }

}

这个方面在合适的时候调用了跟踪方法。根据此方面,跟踪方法在Shape等级中每个方法或构造子的入口和出口处调用,输出的是各个方法的签名。因为方法签名是静态信息,我们可以利用thisJoinPointStaticPart对象获得。运行这个方面的main方法可以获得以下输出:

  --> tracing.TwoDShape(double, double)

  <-- tracing.TwoDShape(double, double)

  --> tracing.Circle(double, double, double)

  <-- tracing.Circle(double, double, double)

  --> tracing.TwoDShape(double, double)

  <-- tracing.TwoDShape(double, double)

  --> tracing.Circle(double, double, double)

  <-- tracing.Circle(double, double, double)

  --> tracing.Circle(double)

  <-- tracing.Circle(double)

  --> tracing.TwoDShape(double, double)

  <-- tracing.TwoDShape(double, double)

  --> tracing.Square(double, double, double)

  <-- tracing.Square(double, double, double)

  --> tracing.Square(double, double)

  <-- tracing.Square(double, double)

  --> double tracing.Circle.perimeter()

  <-- double tracing.Circle.perimeter()

c1.perimeter() = 12.566370614359172

  --> double tracing.Circle.area()

  <-- double tracing.Circle.area()

c1.area() = 12.566370614359172

  --> double tracing.Square.perimeter()

  <-- double tracing.Square.perimeter()

s1.perimeter() = 4.0

  --> double tracing.Square.area()

  <-- double tracing.Square.area()

s1.area() = 1.0

  --> double tracing.TwoDShape.distance(TwoDShape)

    --> double tracing.TwoDShape.getX()

    <-- double tracing.TwoDShape.getX()

    --> double tracing.TwoDShape.getY()

    <-- double tracing.TwoDShape.getY()

  <-- double tracing.TwoDShape.distance(TwoDShape)

c2.distance(c1) = 4.242640687119285

  --> double tracing.TwoDShape.distance(TwoDShape)

    --> double tracing.TwoDShape.getX()

    <-- double tracing.TwoDShape.getX()

    --> double tracing.TwoDShape.getY()

    <-- double tracing.TwoDShape.getY()

  <-- double tracing.TwoDShape.distance(TwoDShape)

s1.distance(c1) = 2.23606797749979

  --> String tracing.Square.toString()

    --> String tracing.TwoDShape.toString()

    <-- String tracing.TwoDShape.toString()

  <-- String tracing.Square.toString()

s1.toString(): Square side = 1.0 @ (1.0, 2.0)

 

Tracing版本二

       版本二实现了可重用的tracing方面,使其不仅仅用于Shape的例子。首先定义如下的抽象方面Trace.java

abstract aspect Trace {

 

    public static int TRACELEVEL = 2;

    public static void initStream(PrintStream s) {...}

    protected static void traceEntry(String str) {...}

    protected static void traceExit(String str) {...}

abstract pointcut myClass();

 

}

为了使用它,我们需要定义我们自己的子类。

public aspect TraceMyClasses extends Trace {

    pointcut myClass(): within(TwoDShape) || within(Circle) || within(Square);

 

    public static void main(String[] args) {

        Trace.TRACELEVEL = 2;

        Trace.initStream(System.err);

        ExampleMain.main(args);

    }

}

注意我们仅仅在类中声明了一个切点,它是超类中声明的抽象切点的具体实现。版本二的Trace类的完整实现如下

abstract aspect Trace {

 

    // implementation part

 

    public static int TRACELEVEL = 2;

    protected static PrintStream stream = System.err;

    protected static int callDepth = 0;

 

    public static void initStream(PrintStream s) {

        stream = s;

    }

    protected static void traceEntry(String str) {

        if (TRACELEVEL == 0) return;

        if (TRACELEVEL == 2) callDepth++;

        printEntering(str);

    }

    protected static void traceExit(String str) {

        if (TRACELEVEL == 0) return;

        printExiting(str);

        if (TRACELEVEL == 2) callDepth--;

    }

    private static void printEntering(String str) {

        printIndent();

        stream.println("--> " + str);

    }

    private static void printExiting(String str) {

        printIndent();

        stream.println("<-- " + str);

    }

    private static void printIndent() {

        for (int i = 0; i < callDepth; i++)

            stream.print("  ");

    }

 

    // protocol part

 

    abstract pointcut myClass();

 

    pointcut myConstructor(): myClass() && execution(new(..));

    pointcut myMethod(): myClass() && execution(* *(..));

 

    before(): myConstructor() {

        traceEntry("" + thisJoinPointStaticPart.getSignature());

    }

    after(): myConstructor() {

        traceExit("" + thisJoinPointStaticPart.getSignature());

    }

 

    before(): myMethod() {

        traceEntry("" + thisJoinPointStaticPart.getSignature());

    }

    after(): myMethod() {

        traceExit("" + thisJoinPointStaticPart.getSignature());

    }

}

它与版本一的不同包括几个部分。首先在版本一中Trace用单独的类来实现而方面是针对特定应用实现的,而版本二则将Trace所需的方法和切点定义融合在一个抽象方面中。这样做的结果是traceEntrytraceExit方法不需要看作是公共方法,它们将由方面内部的通知调用,客户完全不需要知道它们的存在。这个方面的一个关键点是使用了抽象切点,它其实与抽象方法类似,它并不提供具体实现而是由子方面实现它。

 

Tracing版本三

       在前一版本中,我们将traceEntrytraceExit方法隐藏在方面内部,这样做的好处是我们可以方便的更改接口而不影响余下的代码。

       重新考虑不使用AspectJ的程序。假设,一段时间以后,tracing的需求变了,我们需要在输出中加入方法所属对象的信息。至少有两种方法实现,一是保持traceEntrytraceExit方法不变,那么调用者有责任处理显示对象的逻辑,代码可能如下

       Trace.traceEntry("Square.distance in " + toString());

另一种方法是增强方法的功能,添加一个参数表示对象,例如

  public static void traceEntry(String str, Object obj);

  public static void traceExit(String str, Object obj);

然而客户仍然有责任传递正确的对象,调用代码如下

       Trace.traceEntry("Square.distance", this);

这两种方法都需要动态改变其余代码,每个对traceEntrytraceExit方法的调用都需要改变。

       这里体现了方面实现的另一个好处,在版本二的实现中,我们只需要改变Trace方面内部的一小部分代码,下面是版本三的Trace方面实现

abstract aspect Trace {

 

    public static int TRACELEVEL = 0;

    protected static PrintStream stream = null;

    protected static int callDepth = 0;

 

    public static void initStream(PrintStream s) {

        stream = s;

    }

 

    protected static void traceEntry(String str, Object o) {

        if (TRACELEVEL == 0) return;

        if (TRACELEVEL == 2) callDepth++;

        printEntering(str + ": " + o.toString());

    }

 

    protected static void traceExit(String str, Object o) {

        if (TRACELEVEL == 0) return;

        printExiting(str + ": " + o.toString());

        if (TRACELEVEL == 2) callDepth--;

    }

 

    private static void printEntering(String str) {

        printIndent();

        stream.println("Entering " + str);

    }

 

    private static void printExiting(String str) {

        printIndent();

        stream.println("Exiting " + str);

    }

 

    private static void printIndent() {

        for (int i = 0; i < callDepth; i++)

            stream.print("  ");

    }

 

    abstract pointcut myClass(Object obj);

 

    pointcut myConstructor(Object obj): myClass(obj) && execution(new(..));

    pointcut myMethod(Object obj): myClass(obj) &&

        execution(* *(..)) && !execution(String toString());

 

    before(Object obj): myConstructor(obj) {

        traceEntry("" + thisJoinPointStaticPart.getSignature(), obj);

    }

    after(Object obj): myConstructor(obj) {

        traceExit("" + thisJoinPointStaticPart.getSignature(), obj);

    }

 

    before(Object obj): myMethod(obj) {

        traceEntry("" + thisJoinPointStaticPart.getSignature(), obj);

    }

    after(Object obj): myMethod(obj) {

        traceExit("" + thisJoinPointStaticPart.getSignature(), obj);

    }

}

在此我们必须在methods切点排除toString方法的执行。问题是toString方法在通知内部调用,因此如果我们跟踪它,我们将陷入无限循环中。这一点不明显,所以必须在写通知时格外注意。如果通知回调对象,通常都回存在循环的可能性。

       事实上,简单的排除连接点的执行并不够,如果在这之中调用了其他跟踪方法,那么就必须提供以下限制

&& !cflow(execution(String toString()))

排除toString方法的执行以及在这之下的所有连接点。

       总之,为了实现需求的改变我们必须在Trace方面中做一些改变,包括切点说明。但是实现的改变只局限于Trace方面内部,而如果没有方面,则需要更改每个应用类的实现。

 

更多信息

       1AspectJ安装和配置指南

       2跟我学AspectJ(一)

 

参考资料

       The AspectJTM Programming Guide  http://www.eclipse.org/aspectj/

 

如果需要转贴请写名作者和出处。

 

跟我学aspectj之五 ----- args带参数的pointcut

上一节,我们overview了一下pointcut的类型,怎么够多吧? 不夸张的说基本涵盖了Java程序的所有生命周期。这就以为着:我们可以控制到一个已经存在的Java程序的任何地方和环节。可能你还不...

【第六章】 AOP 之 6.5 AspectJ切入点语法详解 ——跟我学spring3

6.5.1  Spring AOP支持的AspectJ切入点指示符        切入点指示符用来指示切入点表达式目的,,在Spring AOP中目前只有执行方法这一个连接点,Spring AOP支...

跟我学aspectj之十一 ----- target() this() within()的区别

keyword: target this within  difference  区别           经过上面的一个阶段的学习,你可能已经知道了这3个关键字,而且多是用来做过滤的。你是...

【第六章】 AOP 之 6.4 基于@AspectJ的AOP ——跟我学spring3

Spring除了支持Schema方式配置AOP,还支持注解方式:使用@AspectJ风格的切面声明。 6.4.1  启用对@AspectJ的支持        Spring默认不支持@Aspe...

跟我学aspectj之四 ----- pointcut基础语法

一、aspect的定义    运行完HelloWorld以后,我们来看下aspect的基础语法: 1、定义一个切面: 关键字aspect。 这定义Java类的语法类似。 2、定义pointc...

【第六章】 AOP 之 6.5 AspectJ切入点语法详解 ——跟我学spring3

6.5  Spring AOP支持的AspectJ切入点指示符        切入点指示符用来指示切入点表达式目的,在Spring AOP中目前只有执行方法这一个连接点,Spring AOP支持...

跟我学aspectj之八 -----控制流cfow,cfowbelow

一、序言      cflow我认为是aspectj中最难理解的一个概念,至少我是这么认为的。当初刚接触aspectj的时候,可谓是为之颠倒,不只大家是否有相同的感觉。但有一点不可否认的就是:他觉得...

【第六章】 AOP 之 6.4 基于@AspectJ的AOP ——跟我学spring3

Spring除了支持Schema方式配置AOP,还支持注解方式:使用@AspectJ风格的切面声明。 6.4.1  启用对@AspectJ的支持        Spring默认不支持@Aspe...

【第六章】 AOP 之 6.5 AspectJ切入点语法详解 ——跟我学spring3

6.5.1  Spring AOP支持的AspectJ切入点指示符        切入点指示符用来指示切入点表达式目的,,在Spring AOP中目前只有执行方法这一个连接点,Spring AO...

【第六章】 AOP 之 6.4 基于@AspectJ的AOP ——跟我学spring3

Spring除了支持Schema方式配置AOP,还支持注解方式:使用@AspectJ风格的切面声明。 6.4.1  启用对@AspectJ的支持        Spring默认不支持@Aspect...
  • zhaoshe
  • zhaoshe
  • 2013年03月04日 14:24
  • 233
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:跟我学AspectJ(五)
举报原因:
原因补充:

(最多只允许输入30个字)