Fall 2002 Programming Language Seminar:
Aspect-Oriented Programming with AspectJ

Back to main course page

[ Washington University in St Louis ]
Department of Computer Science

Course Notes: 18 September 2002

Code examples from today's lecture:


Brief review of last time

Example: TraceCounter

Consider the TraceCounter aspect (also available here):

aspect TraceCounter {
  Hashtable joinPointsSeen = new Hashtable();

  pointcut methodCall() : call(* Foo.*(..));
  pointcut constructorCall() : call(Foo.new(..));

  pointcut outsideCall() :
    (methodCall() || constructorCall()) && !within(Foo);

  pointcut interestingJoinPoints() :
    outsideCall() || within(Foo);

  pointcut mainExecution() :
    execution(public static void Foo.main(String[]));

  before() : interestingJoinPoints() {
    Integer count = (Integer)joinPointsSeen.get(thisJoinPoint.toString());
    if(count == null)
      count = new Integer(1);
    else count = new Integer(count.intValue() + 1);
    joinPointsSeen.put(thisJoinPoint.toString(), count);
  }

  after() : mainExecution() {
    Enumeration e = joinPointsSeen.keys();
    while(e.hasMoreElements()) {
      String jpString = (String)e.nextElement();
      Integer count = (Integer)joinPointsSeen.get(jpString);
      System.out.println(count + " " + jpString);
    }
  }
}

First, we declare a reference to a Hashtable named joinPointsSeen. This will come in useful later.

Next, the pointcut interestingJoinPoints is built up in stages:

  1. The pointcut methodCall() is defined to be the set of all join points that represent calls to methods of class Foo.
  2. The pointcut constructorCall() is defined to be the set of all join points that represent calls to constructors of class Foo.
  3. The pointcut outsideCall is defined to be any method call or constructor call that occurs outside of Foo.
  4. Finally, the point cut interestingJoinPoints is composed of all outside calls and all join points inside of class Foo. In other words, it is capturing all join points that deal with Foo, both inside and outside the actual code of Foo.
Q: Why go to all this trouble? Isn't it redundant to exclude join points in Foo by writing !within(Foo) just to re-include them with within(Foo)?

A:

Yes. However, it is useful to segregate our pointcut definitions like this for two reasons:
  1. Readability of the code. Because of the pointcut names we've picked, it's clear from the above that our join points of interest (interestingJoinPoints) are outside calls to Foo plus join points inside of Foo. If the definition of interestingJoinPoints were just listed out from primary pointcut designators, this fact would be obscured.
  2. Reusability of join points. Either this aspect or another (related) aspect may choose to re-use the methodCall(), constructorCall(), or outsideCall definitions for other pieces of advice. Providing this extensibility is often a good idea even when these pointcuts aren't currently used in the code. (There is no runtime execution or memory penalty for the above pointcut specifications, since they only make static assertions about the code.)

The last pointcut defined is mainExecution(). This pointcut is simple -- it represents the execution of Foo.main().

Once the pointcuts are all defined, we write two pieces of advice. The first piece of advice executes at our interestingJoinPoints and counts the number of join points of each type we see. The second piece of advice runs after Foo.main() exits and prints a report of the join points seen. The output printed in this case is:

Start of main
Hello from Foo.someMethod
End of main
Hello from Foo.someMethod
Hello from Bar.anotherMethod
2 execution(Foo())
4 call(void java.io.PrintStream.println(String))
2 call(void Foo.someMethod())
1 staticinitialization(Foo.<clinit>)
4 get(PrintStream java.lang.System.out)
2 call(Foo())
1 call(void Bar.anotherMethod())
1 call(Bar())
1 execution(void Foo.main(String[]))
2 execution(Foo.<init>)
2 initialization(Foo())
2 execution(void Foo.someMethod())

Each line of the report specifies the count and the join point string (which you can get by calling thisJoinPoint.toString() within advice).

Q: Is TraceCounter thread-safe?

A:

TraceCounter is not thread-safe.

java.util.Hashtable was engineered to be synchronized (unlike the new Java Collection hierarchy). However, the advice that gets the Integer from the Hashtable then adds and sets it doesn't properly accommodate multiple threads. This entire sequence of operations (between get and set would need to be atomic for -- this can easily be done with a synchronized block. Further, simple exiting from Foo.main() is no longer a sufficient termination condition. We now need to ensure that all threads have completed before we write our report to the screen. We can either do this by registering a shutdown hook (with Runtime.addShutdownHook()) or by keeping track of running threads and firing when they all complete.

Example: RealSquareRootExample

Consider the RealSquareRootExample class and associated aspect below (also available here):

class RealSquareRootExample {
  public static void main(String[] args) {
    System.out.println("sqrt(13.0) is " + Math.sqrt(13.0));
    System.out.println("sqrt(9.0) is " + Math.sqrt(9.0));
    System.out.println("sqrt(-4.0) is " + Math.sqrt(-4.0));
  }
}

aspect EnsureRealSquareRoot {
  before(double d) : call(static double Math.sqrt(double)) && args(d) {
    if(d < 0.0)
      throw new IllegalArgumentException("Positive arguments to sqrt() only, please!");
  }
}

Here we have an aspect that is preventing us from calling java.lang.Math.sqrt() with a negative double argument. When a double argument is passed to Math.sqrt().

Note that this example introduces an aspect effect on the Java standard library type Math. Well, sort of. Since this is a call point cut, we're actually only affecting the RealSquareRootExample code, not the java.lang.Math code. If we wanted to weave an aspect's quantifications into java.lang.Math with the present version of the AspectJ tools, we would need to recompile java.lang.Math along with our aspect. A discussion of these implementation limitations is available here.

Q: It seems like I should be able to put the if test for negative argument in the point cut rather than in the advice code. Can I?

A:

This was asked in-class. The answer is yes, using the if pointcut designator:
aspect EnsureRealSquareRoot {
  before(double d) : call(static double Math.sqrt(double))
                     && args(d) && if(d < 0.0) {
    throw new IllegalArgumentException(
            "Positive arguments to sqrt() only, please!");
  }
}

The AspectJ compiler is essentially just adding the if test where it was in the first case, but you can make use of if tests in pointcuts if they make sense conceptually.

Example: PrintFoo

Consider the following code (example also available here):

class A {
  void foo() {
  }

  public static void main(String[] args) {
    A a = new A();
    a.foo();
  }
}

aspect PrintFoo {
  before() : call(* *.*(..)) && this(Object) && !within(asp) {
    System.out.println("foo");
  }
}

Does "foo" get printed or not? Explain.

Although it looks as though "foo" does get printed, it actually does not. This is because of the definition of the this() designator -- this(Object) picks out those join points where (this instanceof Object) is true. Since the call a.foo() above occurs within the static method main, there is no this object at that point. Thus this(Object) doesn't pick out this join point, and it is not in the pointcut at which the before() advice executes.

This points out an important difference between the pointcuts call(* *.*(..)) && within(A) and call(* *.*(..)) && this(A) -- the former will pick out calls that occur within static methods.

Q: So, why wasn't the comparison made between call(* *.*(..)) && this(A) and call(* A.*(..)) ?

A:

Remember that call(* A.*(..)) is the set of call join points where some method of A is being called. This is distinct from call(* *.*(..)) && this(A) , which is the set of all call join points where and also (this instanceof A) is true. It is also distinct from call(* *.*(..)) && within(A) , which is the set of all call join points that occur (statically) within the code of class A (i.e., between the { and } braces defining class A).

Example: RestrictedQuadrantExample

Consider the following code (example also available here):

class Point {
  public int x, y;
}

class Quadrant1Point extends Point { }

aspect QuadrantEnforcer {
  int around(int v) : set(int Point.x) && target(Quadrant1Point) && args(v) {
    if(v >= 0) {
      System.out.println(v + " is legal");
      return proceed(v);
    } else {
      System.out.println(v + " is ILLEGAL");
      return proceed(-v);
    }
  }
}

Now, what happens if we have the following driver?

class RestrictedQuadrantExample {
  public static void main(String[] args) {
    Point p;

    System.out.println("regular points:");
    p = new Point();
    p.x = 5;
    System.out.println("p.x is now " + p.x);
    p.x = -1;
    System.out.println("p.x is now " + p.x);

    System.out.println("quadrant1 points:");
    p = new Quadrant1Point();
    p.x = 4;
    System.out.println("p.x is now " + p.x);
    p.x = -10;
    System.out.println("p.x is now " + p.x);
  }
}

In the first sequence of p.x assignments, p is not an instanceof Quadrant1Point, so the advice doesn't fire, and all assignments are permitted. In the second sequence of p.x assignments, however, p is an instanceof Quadrant1Point, so the advice from QuadrantEnforcer does apply, and the second assignment, p.x = -10, is illegal.

This analysis is verified by the output:

regular points:
p.x is now 5
p.x is now -1
quadrant1 points:
4 is legal
p.x is now 4
-10 is ILLEGAL
p.x is now 10

Please contact me at mdeters@cse.wustl.edu if you have any questions regarding these notes.

Help on setting up AspectJ
Back to AspectJ Seminar course calendar


Morgan Deters / About me / OpenPGP Public Key / 02 Jan 2006

This page is certified valid HTML 4.01!  This page refers to certified valid CSS!  I support the AnyBrowser campaign