Aspect-oriented programming a la AspectJ is known to cause some
modularity problems. In particular, aspects can change the behaviour of
classes without the classes knowing, and the programmers of classes must
observe interfaces defined by aspects unknown to them. The following
figure shows the current dependencies and the single-sided interface
between aspect X
and classes A
, B
,
and C
. Note that the interface materializes in the pointcut
of the aspect. Although the pointcut can be moved out of the aspect into
another one or even to a class, the affected classes contain no
reference to it.
We offer a solution that avoids these problems, at the price of limiting AspectJ's expressiveness. In particular, the usefulness for vastly crosscutting concerns such as logging and tracing is restricted. The solution is based on join points as instances of join point types exhibited by classes and advised by aspects, and polymorphic (i.e., per class) definitions of pointcuts. It achieves a form of decoupling that is comparable to that of event-driven programming, with remaining dependencies and corresponding interfaces as shown in the following figure.
Note that the pointcuts are now defined within the classes and thus the classes' implementation secrets. It follows that classes and aspects are fully encapsulated behind the join point type interface and can hence evolve independently. In other words: classes and aspects are now modular.
Worries that our suggestion leads to duplicate code, namely
identical pointcuts scattered around a program, can be countered by
letting classes refer to pointcuts of their superclasses using the
keyword super
. In any case, a class offering its join
points for advising will know the pointcuts that specify them.
Construct | Syntax | Semantics |
---|---|---|
join point type | [public|final|abstract] joinpointtype <name> |
A join point type is the intensional specification of a set of join points. It consists of a set of fields which bind to variables of the context in which a join point occurs. Its type predicate, a pointcut, is deferred to classes exhibiting join points of that type and is a disjunction of the branches defined therein. Fields may be declared as final to avoid reassignment.
Analogous to normal Java classes a joinpointtype can have different
modifier. Thereby, the different modifier have the same meaning as
they have for a class. Leaving the |
polymorphic pointcut | pointcut <join point type> : |
A local branch (disjunct) of the pointcut (type predicate)
associated with the join point type <join point
type> whose scope is restricted to the class containing the
polymorphic pointcut definition. The local pointcut maps the fields
of <join point type> to variables used in the
class definition (instance variables, method parameters etc.). The
pointcut of <join point type> is then a
disjunction of the local branches defined in various classes.The pointcut of the same join point type of the superclass can be referenced using the keyword super . |
join point exhibition | class <class name> ... exhibits <join
point type names> |
A so-declared class announces that it exhibits (makes
visible) join points of all types listed in <join
point type names> . It must provide one local pointcut for each
exhibited join point type. |
join point advising | aspect <aspect name> advises <join
point types> |
A so-declared aspect announces to advise join points of all
types listed in <join point types> . It must
provide one advice for each exhibited join point type; advice for
join point supertypes counts as advice for its subtypes. |
advice binding | <kind> (<join point formal>)
{<advice body>} |
Within an aspect, this binds a join point type to an advice
body. <kind> is one of AspectJ's advice kinds such as
before() , after() , around() ,
etc. (see AspectJ documentation for detailed information). The formal parameter represents the join point instance within the advice. The fields of the join point can be accessed with <join point
name>.<field name> . |
calls to proceed |
proceed(<join point type instance>); |
In proceed calls the join point type instance is used as arguments to pass the contex information. |
anonymous join point type | exhibit new <join point
type>(<parameters>) |
Creates an new join point instance of <join
point type> for <statement> , where the actuals
<parameters> are bound to the fields of the join
point. The type predicate (pointcut) of this join point type, which
is considered an anonymous subtype of <join point
type> , remains implicit it matches just this one statement. |
The full syntax description in EBNF can be found here
Toplanguage construct | conservative | progressive |
---|---|---|
exhibit clause | The exhibit clause (like in class A exhibits JP {...} ) is not inherited to subclasses. |
The exhibit clause is inherited to subclasses. |
polymorphic pointcuts | Polymorphic pointcuts are not inherited to subclasses. | Polymorphic pointcuts are inherited to subclasses, but the subclass can overwrite the pointcut declaration. |
We have implemented a compiler for our language as an extension to the AspectBench Compiler (abc). We have called our extension IIIA, which is short for Implicit Invocation with Implicit Announcement.
An archive including the original abc-library, our IIIA extension for the abc, and a couple of examples is available from here. It unpacks to 2 new subdirectory named:
Notice:
The compiler needs a Java Runtime Environment (JRE) Version 1.5 or
higher.
If you don't have a proper JRE installed, you can download it from http://java.sun.com.
To build and run our examples, we recommend to use Ant. An Ant script is included in the
directory abc-ja-iiia-test
with which the examples can be tried out easily. Simply type
ant -p
to get a list of available Ant targets. For instance, to build and run
the shopping session example
type:
ant run-example-buying
To use the progressive mode of the compiler you have to pass the VM-argument
-Dmode=progressive
to the compiler. This you can do by using the
enviroment variable ANT_OPTS
.
To use IIIA without Ant, or to compile and run your own code, we have included in the package two scripts for compiling and running:
iiia-c
to use the compileriiia
to run compiled codeTo start the compiler, type in the directory abc-ja-iiia-test
:
iiia-c <directory with source files> <output
directory>
where the first directory defines the directory containing the source
files (*.java) and the second directory defines the directory in which
the compiler puts the compiled files (*.class). For example, to compile
the buying example type:
iiia-c example-buying/src example-buying/bin
To run code building on IIIA, type
iiia <class file directory> <start class>
To execute the shopping
session example, type:
iiia example-buying/bin application.Test
To invoke the compiler in the progressive mode the scripts include the environment
JAVA_OPTS
. This variable can be used to pass the VM-argument
-Dmode=progressive
to the compiler.
If you want to include additional libraries in your class path,
you have to call the compiler manually. You can start the compiler by
typing :
java -cp ../abc-ja-iiia-libs/abc-ja-complete.jar;../abc-ja-iiia-libs/abc-ja-iiia-jar;<your
own jars> abc.main.Main -ext abc.iiia -sourceroots <source
directory> -d <output directory>
Note that the class path entries are
separated with ":" instead of ";" on *NIX systems.
In order to execute the code you have to make sure that
the abc-runtime.jar is in your classpath (our abc-ja-iiia.jar is
not necessary). To run the code you have to type :
java -cp ../abc-ja-iiia-libs/abc-runtime.jar;<directory with class
files> <start class>
Refer to the abc documentation for further instructions on how to use abc.
In order to invoke the compiler in the progressive mode you have to pass the VM-argument
-Dmode=progressive
to the compiler.
After
advice cannot change variables in the
context of a join point. This is a problem inherited from AspectJ
(because proceed
is the only way to affect context, and
all changes to variables after a proceed
have no effect on
the context).thisJoinPoint
,
thisJoinPointStaticPart
, ...) but does not apply anywhere
will cause an IndexOutOfBounceException
. We have reported
this issue to the abc-Team.
Our first example is typical for implicit invocation with implicit announcement (IIIA). It introduces it as a concept that is better known in the database community under the name of trigger (see also below).
The full example can be found here
joinpointtype CheckingOut
joinpointtype CheckingOut { // going to take this item and amount from stock Item item; int amount; }
joinpointtype Buying
joinpointtype Buying extends CheckingOut { // buying this amount of item }
class ShoppingSession
class ShoppingSession exhibits CheckingOut,Buying { pointcut CheckingOut : call(* add(..)) && args(item, amount,..); pointcut Buying : call(* add(..)) && args(item, amount) && withincode(void buy(..)); ShoppingCart sc = new ShoppingCart(); Invoice inv = new Invoice(); Log log = new Log("Shopping-Session-Log"); Customer cus = customerLogOn(); int totalAmount = 0; void buy(Item item, int amount) { System.out.println("ShoppingSession.buy("+item+","+amount+")"); sc.add(item, amount); // Matched by CheckingOut, Buying inv.add(item, amount, cus); log.add(item, amount, cus); exhibit new Buying(item, amount) { // Matched by Buying totalAmount += amount; }; System.out.println("totalAmount="+totalAmount); } void rent(Item item, int amount, java.util.Date returndate) { sc.add(item, amount); // Matched by CheckingOut log.rent(item, amount, cus); } Customer customerLogOn() {return new Customer("Test "+System.currentTimeMillis());} }
class ShoppingCart
class ShoppingCart /*exhibits Buying*/ { // pointcut Buying : execution(* add(Item, int)) && args(item, amount); void add(Item it, int amount) { System.out.println("ShoppingCart.add("+it+","+amount+")"); } }
class Log
class Log /*exhibits Buying*/ { // pointcut Buying : execution(* add(Item, int, ..)) && args(item, amount, ..); String log; Log(String log) {...} void add(Item item, int amount, Customer c) {...} void rent(Item item, int amount, Customer c) {...} }
class Item
class Item {...}
class Invoice
class Invoice {...}
class Customer
class Customer {...}
aspect BusinessRules
aspect BusinessRules advises CheckingOut { before(CheckingOut co) { System.out.println("\t[check]\tCheckingOut : before "+thisJoinPoint+" -> "+co.getClass()); System.out.println("\t[check]\tcheck if Stock is sufficient : Stock : "+Stock.amount(co.item)+" amount : "+co.amount); if (Stock.amount(co.item) < co.amount) { System.out.println("\t[check]\t-> Stock is not sufficient for Item : "+co.item); throw new OutOfStockException(co.item); } } }
aspect BusinessRules
aspect BusinessRules advises CheckingOut { before(CheckingOut co) { System.out.println("\t[check]\tCheckingOut : before "+thisJoinPoint+" -> "+co.getClass()); System.out.println("\t[check]\tcheck if Stock is sufficient : Stock : "+Stock.amount(co.item)+" amount : "+co.amount); if (Stock.amount(co.item) < co.amount) { System.out.println("\t[check]\t-> Stock is not sufficient for Item : "+co.item); throw new OutOfStockException(co.item); } } }
aspect Buying
aspect BonusProgram advises CheckingOut, Buying { void around (CheckingOut jp) { System.out.println("\t** BonusProgram: around -> "+jp.getClass()); if (jp.item.category == Item.BOOK) { System.out.println("\t[buy]\t-> Item == BOOK : add "+jp.amount / 2+" to amount "+jp.amount); jp.amount += jp.amount / 2; } proceed(jp); } // This advice is no longer required as the around(CheckingOut) is applied on JP of Type Buying // void around (Buying jp) { // ... // } }
First, the following join point types must be declared:
joinpointtype ConnectionComplete { Connection c; }
joinpointtype ConnectionCreation { Customer cust; }
joinpointtype ConnectionDropped { Connection conn; }
For the rest, we show only the differences between the original program and that adapted to using join point types and polymorphic pointcuts. The complete example is in the our package.
original
public class Call {...
adapted
public class Call exhibits ConnectionComplete, ConnectionDropped, ConnectionCreation { pointcut ConnectionComplete : target(c) && call(void Connection.complete()); pointcut ConnectionDropped : target(conn) && call(void Connection.drop()); pointcut ConnectionCreation : args(cust, ..) && call(Connection+.new(..));
original
public abstract class Connection {...
adapted
public abstract class Connection exhibits ConnectionCreation { pointcut ConnectionCreation : args(cust, ..) && execution(new(..));
original
public class Local extends Connection {...
adapted
public class Local extends Connection exhibits ConnectionCreation { pointcut ConnectionCreation : super;
original
public class LongDistance extends Connection {
adapted
public class LongDistance extends Connection exhibits ConnectionCreation { pointcut ConnectionCreation : super;
original
public aspect Billing { after(Customer cust) returning(Connection conn): args(cust, ..) && call(Connection+.new(..)) {... after(Connection conn): Timing.endTiming(conn) {...
adapted
public aspect Billing advises ConnectionDropped, ConnectionCreation { after(ConnectionCreation creation) returning(Connection c) { c.payer = creation.cust; } after(ConnectionDropped dropped) { long time = Timing.aspectOf().getTimer(dropped.conn).getTime(); long rate = dropped.conn.callRate(); long cost = rate * time; getPayer(dropped.conn).addCharge(cost); }
original
public aspect Timing { after (Connection c): target(c) && call(void Connection.complete()) {... after(Connection c): endTiming(c) {... pointcut endTiming(Connection c): target(c) && call(void Connection.drop());
adapted
public aspect Timing advises ConnectionComplete, ConnectionDropped { after(ConnectionComplete complete) { getTimer(complete.c).start(); } after(ConnectionDropped dropped) { getTimer(dropped.conn).stop(); dropped.conn.getCaller().totalConnectTime += getTimer(dropped.conn).getTime(); dropped.conn.getReceiver().totalConnectTime += getTimer(dropped.conn).getTime(); } // pointcut endTiming(Connection c): ...
That's all.
TopThe following list presents further examples using IIIA:
Conceptually, this work is a continuation of our prior work on type levels for AspectJ, to help avoid accidental recursion of aspects. The project is described here.
TopFriedrich Steimann, Thomas Pawlitzki, Sven Apel, Erich Kästner