跟我学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之三 ----- Hello World

一、创建项目     我们将project命名为:aspectjDemo。然后我们新建2个package:com.aspectj.demo.aspect  和 com.aspectj.demo.t...
  • zl3450341
  • zl3450341
  • 2012年06月18日 17:07
  • 13288

跟我学aspectj之六 ----- 插曲

其实,写这样的文章不太好写, 因为都是牵一发而动全身。介绍pointcut,的时候必要涉及到语法,也会涉及advice,以及一些其他的语法。    所以,如果大家在看博客的过程中,如果有疑问...
  • zl3450341
  • zl3450341
  • 2012年06月18日 17:10
  • 6731

跟我学aspectj之二 ----- 搭建开发环境

一、下载Aspectj以及AJDT     上一章已经列出了他的官方网站,自己上去download吧。AJDT是一个eclipse插件,开发aspectj必装,他可以提供语法检查,以及编译。这里要说...
  • zl3450341
  • zl3450341
  • 2012年06月18日 17:07
  • 18920

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

一、aspect的定义    运行完HelloWorld以后,我们来看下aspect的基础语法: 1、定义一个切面: 关键字aspect。 这定义Java类的语法类似。 2、定义pointc...
  • zl3450341
  • zl3450341
  • 2012年06月18日 17:09
  • 13152

跟我学AspectJ(一)

跟我学AspectJ(一) 编者的话       关于AspectJ的开发资料好象目前还只有英文版的,而且还不是很多,这对于有兴趣学习AOP而英语不是很好的开发人员来是一件很苦闷的事情,所以我决定总结...
  • starchu1981
  • starchu1981
  • 2003年07月22日 07:58
  • 17271

跟我学aspectj之七 ----- call,execution,within,withincode

本节开始,我们将详细介绍pointcut的语法 一、call和execution    语法结构:execution([修饰符] 返回值类型 方法名(参数) [异常模式])  蓝色表示可选部分...
  • zl3450341
  • zl3450341
  • 2012年06月18日 17:10
  • 12295

跟我学AspectJ

跟我学AspectJ
  • zlllxl2002
  • zlllxl2002
  • 2015年02月27日 17:03
  • 199

跟我学aspectj之一 ----- 简介

一、为什么写这个系列的博客        Aspectj一个易用的、功能强大的aop编程语言。其官网地址是:http://www.eclipse.org/aspectj/,目前最新版本为:1.7...
  • highway61revisited
  • highway61revisited
  • 2015年05月31日 09:00
  • 123

跟我学AspectJ(二)

跟我学AspectJ(二) 本文继续前篇的内容,将介绍AspectJ的应用范围以及AspectJ的部分基本语言。AspectJ应用范围       如前所述,AspectJ可以用于应用开发的不同阶段。...
  • starchu1981
  • starchu1981
  • 2003年07月27日 11:26
  • 3096

跟我学AspectJ(三)

  第二章          AspectJ语言引语       在本系列的前一章中,我们简要的说明了AspectJ语言的总揽。为了理解AspectJ的语法和语义,你应该阅读本章。这一部分包括了前述的...
  • starchu1981
  • starchu1981
  • 2003年08月25日 09:08
  • 2637
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:跟我学AspectJ(五)
举报原因:
原因补充:

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