Section 6.2. THE ASPECTJ LANGUAGE


6.2. THE ASPECTJ LANGUAGE

The AspectJ language is designed as an extension to the Java language. It adds one fundamental conceptjoin points. On top of that are layered AspectJ's pointcut and advice mechanisms. In addition, AspectJ supports a form of open-class composition through a mechanism known as inter-type declarations. Pointcuts, advice, and inter-type declarations can be combined as needed and encapsulated inside a unit of modularity (a type) known as an aspect.

6.2.1. Join Points

Join points are identifiable points (events) in the execution of a program. Not all such points are interesting: For example, AspectJ exposes join points for the execution of a method and for a call made to a method, but not for the execution of the ninth line of source code in a source file. AspectJ's join point model determines the types of join points that can be referred to in an AspectJ program (via pointcut expressions). The join point model defines join points for:

  • The execution of a method or constructor

  • Execution of advice

  • A call to a method or constructor

  • Read or write access to a field

  • The execution of an exception handler (catch block)

  • The static initialization of a class

  • Initialization of an object or aspect

While some of these may seem to be static, syntactic elements of a program's text, it is important to understand that join points are dynamic: they arise during program execution. Thus, the best way to illustrate join points is not a static model of the structure of a system, but through an object collaboration or sequence diagram. Figure 6-1 illustrates execution and call join points in a simple sequence diagram.

Figure 6-1. Execution and call join points shown in a sequence diagram.


6.2.2. Pointcuts

Pointcuts are predicates that match join points. Pointcut expressions are built up using AspectJ's primitive pointcut designators, of which there are three basic types. The first set of pointcut designators matches based on the kind of join point (is it a method call or a field access?). The second set of designators matches join points based on context at the join point (is the executing object an instance of Foo?), and the third set of designators matches based on scope (is the join point resulting from code within the org.xyz package?).

AspectJ provides pointcut designators that match based on kind for each kind of join point in AspectJ's join point model. The designators are: call, execution, get, set, handler, adviceexecution, staticinitialization, preinitialization, and initialization. Wildcarding within pointcut expressions is allowed. Table 6-1 shows some example pointcut expressions and their meanings in AspectJ.

Table 6-1. Example Pointcut Expressions Matching on Kind of Join Point

Pointcut Expression

Meaning

execution(public * get*(..))

The execution of any public method returning any type and taking any arguments, where the method name begins with "get."

set(int Point.x)

An update to the value of the field x in the Point class.

handler(IOException+)

The excecution of an exception handler for IOException or any of its subclasses.

call(* org.xyz..*(..))

A call to any method declared in a type in a package whose name begins with "org.xyz."

staticinitialization (Serializable+)

The static initialization of any serializable class.


AspectJ's context matching pointcut designators are this, target, and args. The expression this(String) matches any join point where the currently executing object is an instance of String. The expression target(Foo) matches any join point where the target object (of a call for example) is an instance of Foo. Referring to the sequence diagram of Figure 6-1, for the withdraw call made by the "clearing house" object to the "account 1" object, the "clearing house" object is bound to this, and the "account 1" object is bound to target.

AspectJ's scoping pointcut designators are within, withincode, cflow, and cflowbelow. The within pointcut matches any join point where the associated source code is defined within a given scope (such as a type or package). The withincode pointcut matches join points where the associated source code is defined within a given set of methods or constructors. The cflow and cflowbelow pointcuts match join points based on the control flow in which they occur. For example, the pointcut expression cflow(execution(* IBankAccount.*(..)) matches any join point arising as a result of the execution of a method defined by the IBankAccount interface, including all join points in the method's call graph. Referring again to Figure 6-1, both the calls to and the execution of the withdraw and deposit methods are in the control flow of the execution of the TRansfer method. Both cflow and cflowbelow below take as their argument another pointcut expression. They differ in that cflow also matches the join points matched by its pointcut expression, whereas cflowbelow excludes those join points.

AspectJ provides one final primitive pointcut, if, that takes a Boolean expression as its argument. The if pointcut can be used to extend AspectJ's join point matching. A common use is to test a Boolean flag, as, for example, in the expression if(enabled).

An important part of the AspectJ language is that pointcuts support abstraction and composition. Abstraction is supported through the pointcut keyword that allows the programmer to define a named pointcut. Composition is achieved by combining both named and primitive pointcuts using the &&, ||, and ! operators. Listing 6-1 shows a simple example of the power this gives: expressions can be easily built up, and the illegalJDBCCall pointcut reads naturally.

Listing 6-1. Pointcut abstraction and composition
 pointcut jdbcCall() :   call(* java.sql..*(..)) || call(* javax.sql..*(..)); pointcut inDataLayer() :   within(org.xyz.persistence..*); pointcut illegalJDBCCall() :   jdbcCall() && !inDataLayer(); 

As well as simply matching join points, pointcuts can provide contextual information about those join points to their consumers. A pointcut can declare a formal parameter list of context it provides, and the contextual values are extracted based on name binding, as in the following example:

 pointcut ejbCall(EJBObject anEJB) :   call(* EJBObject+.*(..)) && target(anEJB); 

AspectJ also provides a declare warning/error mechanism that allows the programmer to enforce certain rules and constraints in their design or implementation by producing a compilation warning or error if a pointcut expression matches any join points. Using the example from Listing 6-1, a programmer could write:

 declare warning : illegalJDBCCall() :   "Don't make database calls outside of data layer"; 

This would cause the compiler to issue a warning if any database calls were coded outside of the data layer.

6.2.3. Advice

Advice is simply a set of program statements that are executed at join points matched by a pointcut. Each piece of advice in AspectJ has an associated pointcut. That pointcut determines the join points at which the advice executes. AspectJ has three basic kinds of advice: before advice, after advice, and around advice. These execute before, after, and around join points matched by the advice's pointcut expression. After advice is further subdivided into after returning advice, after throwing advice, and after (finally) advice. After returning advice executes only on successful return from the join point, and after throwing advice executes only if the join point was exited as a result of throwing an exception. After finally advice always executes however the join point is exited, and thus the programmer must be careful to deal with both normal and exceptional return conditions when using it. The source fragment in Listing 6-2 shows how pointcuts and advice could be combined to implement dirty tracking in a data access object.

Listing 6-2. Dirty tracking example
 /**  * setting a non-transient field in a DataAccessObject  * is considered a dirtying action.  */ pointcut dirtyingAction(DataAccessObj dao) :   set(!transient * *) && target(dao); /**  * after returning from a dirtying action, set  * the dirty flag.  */ after(DataAccessObj dao) returning :   dirtyingAction(dao) {   dao.setDirty(); } pointcut saveOrRestore() :   execution(* DataAccessObj+.save(..)) ||   execution(* DataAccessObj+.restore(..)); /**  * Successful completion of a save or restore  * operation clears the dirty flag.  */ after(DataAccessObj dao) returning :   saveOrRestore() && this(dao) {   dao.clearDirty(); } 

The example also shows how advice can be associated with both named and anonymous pointcut expressions and how context provided by pointcuts can be bound to variables used within the body of the advice. The invocation of advice at join points matched by the pointcut expression associated with it is implicitno program source statements explicitly call the advice. Figure 6-2 shows an example of the implicit invocation of before and after advice to wrap the execution of an account transfer operation in a transaction.

Figure 6-2. Advice invocations shown in a sequence diagram.


Around advice is the most powerful form of advice in AspectJ, since it has the power to decide whether or not a matched join point should actually execute (for example, whether or not a call should proceed). Within the body of around advice, the special form proceed() is called at the point at which the computation at the join point should proceed (if at all). The arguments to the proceed are the same as those in the advice declaration. They can be altered by the body of the advice, affecting, for example, the arguments that a call proceeds with, or even the target object of a call. The example in Listing 6-3 shows the use of around advice to ensure that the value of a field stays within range.

Listing 6-3. Using proceed inside around advice
 /**  * For some markets the engine speed is limited by  * software...  */ void around(int speed) :   set(int Car.currentSpeed) && args(speed) {   int newSpeed = (speed < MAX_SPEED) ? speed : MAX_SPEED;   proceed(newSpeed); } 

6.2.4. Inter-Type Declarations

Sometimes, a crosscutting concern involves not just dynamic considerations (advice) but also static ones. Returning to the theme of dirty tracking in data access objects, the solution presented in Listing 6-2 requires that each data access object support the setDirty() and clearDirty() operations. We would like everything concerned with dirty tracking to be encapsulated in one place, including the definition of these methods. AspectJ's inter-type declaration mechanisms allow an aspect to provide an implementation of methods and fields on behalf of other types. Listing 6-4 shows one way of addressing the static crosscutting elements of dirty tracking using AspectJ.

Listing 6-4. Inter-type declarations for the dirty tracking concern
 (1) interface IDirtyStateTracking {}; (2) (3) declare parents : org.xyz.persistence..* (4)   implements IDirtyStateTracking; (5) (6) private boolean IDirtyStateTracking.isDirty = false; (7) (8) private void IDirtyStateTracking.setDirty() { (9)  isDirty = true; (10) } (11) (12) private void IDirtyStateTracking.clearDirty() { (13)  isDirty = false; (14) } 

Line 1 simply declares an empty interface, IDirtyStateTracking. Lines 3 and 4 use AspectJ's declare parents statement to indicate that all the types in the org.xyz.persistence package should implement the IDirtyStateTracking interface. Line 6 declares that all implementers of the IDirtyStateTracking interface have a field isDirty. This field is declared private to the aspect making the inter-type declaration. Lines 8 through 14 provide a default implementa tion of the setDirty() and clearDirty() operations for implementers of IDirtyStateTracking. Again, these operations are visible only to the aspect making the inter-type declarations. (If they were declared public, then any type in the application could see them.)

In addition to the forms illustrated by this example, AspectJ also supports declare parents ... extends, which changes a type's parent class in a type-safe manner, and a declare soft statement, which can soften exceptions occurring at a given set of join points (used for modularizing certain kinds of exception handling policies).

6.2.5. Aspects

Aspects are the unit of modularity by which AspectJ implements crosscutting concerns. Aspects are declared using the aspect keyword and can contain methods and fields (just like a class), pointcuts, advice, and inter-type declarations. Listing 6-5 shows a complete aspect declaration for a dirty tracking aspect.

Just like classes, aspects can have their own state and behavior defined in methods and fields. Aspect instances are implicitly created by AspectJ at runtime. By default, there is a single instance of each aspect type, but other aspect lifecycles are possible by declaring that an aspect should be created pertarget, perthis, percflow, or percflowbelow of a pointcut expression. For example, using percflow causes a new aspect instance to be created every time a new control flow starts at a join point matched by the pointcut expression.

Listing 6-5. The complete dirty tracking aspect
 public aspect DirtyTracking {   /*    * Marker interface for objects with tracking    */   interface IDirtyStateTracking {};   declare parents : org.xyz.persistence..*     implements IDirtyStateTracking;   // implementation of interface   private boolean IDirtyStateTracking.isDirty = false;   private void IDirtyStateTracking.setDirty() {    isDirty = true;   }   private void IDirtyStateTracking.clearDirty() {    isDirty = false;   }   // setting and clearing dirty flag   pointcut dirtyingAction(IDirtyStateTracking dao) :     set(!transient * *) && target(dao) &&     !within(DirtyTracking);   after(IDirtyStateTracking dao) returning :     dirtyingAction(dao) {     dao.setDirty();   }   pointcut saveOrRestore() :     execution(* IDirtyStateTracking+.save(..)) ||     execution(* IDirtyStateTracking+.restore(..));   after(IDirtyStateTracking dao) returning :     saveOrRestore() && this(dao) {     dao.clearDirty();   } } 

So the aspect declaration:

 public aspect AccountOperation : percflow(execution(* *.*(..)) && target(Account)) {   ... 

creates a new instance of the AccountOperation aspect every time a method is executed on an Account object. The aspect instance lives until the execution of the method has completed.

When several aspects have advice executing at the same join point, the order in which the advice executes is undefined. For many programs, this is perfectly acceptable, but if the order of execution matters, AspectJ provides a declare precedence construct for specifying a (partial) ordering among aspects. Before advice defined in an aspect of higher precedence executes ahead of before advice defined in an aspect of lower precedence; after advice in an aspect of higher precedence executes following after advice of lower precedence.

6.2.6. Further Reading

This has, of necessity, been a brief and incomplete overview of the AspectJ language. Of course, the AspectJ Programming Guide [3] provides a more complete definition of the language. The reader's attention is particularly called to the appendix defining the language's semantics.



Aspect-Oriented Software Development
Aspect-Oriented Software Development with Use Cases
ISBN: 0321268881
EAN: 2147483647
Year: 2003
Pages: 307

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net