Closure Joinpoints for AspectJ
Hint: In August 2011, we have updated our implementation of Closure Joinpoints to match the syntax and semantics of Join Point Interfaces. In result, both implementations now nicely integrate with each other.
Introduction
Closure joinpoints are an extension to AspectJ that allows programmers to mark regions of code explicitly as “to be advised” by aspects. The extension was strongly motivated by related work on explicit joinpoints, and IIIA. Both related work allow aspects to advise labeled block statements in the base program. But blocks are no modular pieces of execution: they are strongly coupled to the context in which they are declared, both in terms of control flow and data flow. For instance, break, continue and return statements bind to the method that hosts the block, not the block itself. When the block executes in the context of a proceed-call, it is unclear what such unstructured jumps would mean. Similarly, statements within a block may update local variables outside the block. Again, the semantics of such updates are unclear when the block executes in a different context.
Closures are anonymous functions, and we believe that they are an abstraction more suited to being exposed as a joinpoint. In our model, closures have only local control flow, confined to the closure, and can only access fields or final local variables from their declaring scope. This language design yields closure joinpoints.
Publications
Closure Joinpoints: Block joinpoints without surprises (Eric Bodden), In AOSD ’11: Proceedings of the 10th international conference on Aspect-oriented software development, pages 117-128, ACM, 2011.
Presentations
This is my talk on Closure Joinpoints, the “Block Joinpoints without surprises”.
This presentation is being made available under the Creative Commons “Attribution-ShareAlike” license.
Download
You can download the implementation here. The ZIP file contains:
- a README file with usage instructions
- the complete source code of our extension
- a compiled binary
- a script to run our implementation on all our test cases.
Example
The following example is taken from related work on IIIA. It is also one of our test cases. In this example, the class ShoppingSession exposes the joinpoint BonusProgram.Buying, which the aspect BonusProgram defines. (In the future, we envision joinpoint types to be defined neither in classes nor in aspects but in a module specification which both parties agree on.) The closure joinpoint has a list of formal parameters (amount and c). These give local names to the variables exposed to the aspect. The code is also instantly called, with the argument list “amount, category” that follows. A closure joinpoint is an expression and can therefore return a single value.
The aspect intercepts joinpoints of type Buying, increasing the amount by half when the exposed Category is BOOK. This way, the aspect implements a “buy two, get one free” special offer for books.
class ShoppingSession {
int totalAmount = 0;
ShoppingCart sc = new ShoppingCart();
void buy(final Item item, int amount) {
Category category = Database.categoryOf(item);
totalAmount = exhibit BonusProgram.Buying(int amount, Category c) { sc.add(item, amount); return totalAmount + amount; } (amount,category);
}
}
aspect BonusProgram {
jpi int Buying(int amount, Category cat);
int around Buying(int amt, Category cat) {
if(cat==Item.BOOK)
amt += amt / 2;
return proceed(amt, cat);
}
}
Implementation details
We implemented Closure Joinpoints as an extension abc.ja.cjp to the AspectBench Compiler (abc) and our source code is also maintained there. Click here to inspect the source code. We implemented Closure joinpoints using the JastAdd frontend and using minimal extensions to abc’s backend (specifically, the matcher). Using JastAdd allowed us to re-use an extract-method refactoring by Schäfer et al. Our implementation works as follows:
- We typecheck closure joinpoints (see below). A joinpoint is an extension of an “anonymous method” AST node (see here) that comes with the refactoring toolkit mentioned above. The only difference between an anonymous method and our definition of a closure joinpoint is that the joinpoint has a name. The refactoring toolkit uses anonymous methods only internally and hence implemented no type checks for them. Further, we implemented typechecks for pieces of advice referencing explicit joinpoints. We implemented the checks in the files TypeCheck* here.
- We use the refactoring to extract each closure joinpoint into a method in the declaring class. Joinpoints within static methods are extracted to static methods, other joinpoints into instance methods. see here (Note: we thought about instead implementing closure joinpoints through anonymous inner classes; however, that would have meant to instantiate a new object of this class at every joinpoint, which is rather costly. Our strategy avoids this issue.)
- We modify regular execution pointcuts so that they do not match the extracted methods: closure joinpoints should only be advised through explicit advice references. This is implemented here.
- For an advice that references a closure joinpoint C, we construct a special pointcut that matches calls to all methods extracted for joinpoint type C. The pointcut has an args pointcut as a child to expose the arguments to this call. See method pointcut() here.
Typing Rules
- Argument and return types: In our initial paper submission, we wrote that return types for closure joinpoints could be covariant, and argument types contravariant, as for normal methods in Java. As De Fraine et al. showed with “StrongAspectJ”, this is not a good semantics in the light of around advice, as it can lead to ClassCastExceptions at runtime. Therefore, we now demand that both return types and argument types be invariant: an “exhibit” clause must name the exact same argument types that are declared in the definition of the joinpoint type. Similarly, an advice advising joinpoints of this type must declare the exact same argument types in its header. This test case here shows what could happen otherwise: the context of the exhibits clause expects a HashSet to be returned, but if we allowed covariant return types then the advice could return any other set, e.g., a TreeSet, which leads to an exception. In future work, we plan to integrate StrondAspectJ’s typing approach with Closure joinpoints and IIIA’s type system.
- Types for after-returning/throwing variables: When an advice has an after-returning parameter of type t and advises a joinpoint type with declared return type r, then r must be cast-convertible to t. Similarly, when an advice has an after-throwing parameter of type t, t is a checked exception type, and the advice advises a joinpoint type with declared Exception types e1…en, then at least one of the e1…en must be cast-convertible to t. see here and here
- Checked Exceptions: The requirement for “invariance” also holds for exceptions. Pieces of advice must be able to handle all exceptions that any block of any “exhibit” clause for the advised joinpoint type may throw. Similarly, all these contexts must be prepared to handle checked exceptions that the advice may throw. We hence extended our syntax to allow joinpoint type definitions to declare a list of checked exceptions. Closures of this type may only throw those checked exceptions and aspects calling such joinpoints via proceed must handle them. (see here and here)
- Captured variables: A closure joinpoint may access its formal parameters and final local variables and fields from the enclosing lexical scope.
- Capture of control flow: A closure joinpoint must not contain break or continue statements whose targets are not part of the same joinpoint.
- “this”: A closure joinpoint has access to “this” if it is defined in a non-static context. “this” always refers to the declaring object, not the aspect instance.
Context exposure and reflection
- this/target/args: thisJoinPoint.getThis() returns this if the joinpoint executes in a non-static context, null otherwise. thisJoinPoint.getTarget() always returns null. thisJoinPoint.getArgs() returns the argument list of the pieces of advice advising the explicit joinpoint. see here, here and here
- returning/throwing: after-returning advice execute when returning from the closure and expose the returned value. Similarly, an after-throwing advice executes whenever the closure throws an exception and exposes the exception value. If the type of the after-returning/throwing parameter is more narrow than the one in the joinpoint-type declaration, then the advice will only match if the return value, respectively the exception is an instance of the parameter’s type. see here and here






