|
Fall 2002 Programming Language Seminar: |
|
Code examples from today's lecture:
Brief review of last time
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:
| 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:
|
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:
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. |
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