AOP@Work: Check out library aspects with AspectJ 5

Write reusable aspects that stack up for all kinds of users

AspectJ 5's new language and deployment features make library aspects easy, and library aspects in turn promise to put AOP in the hands of mere mortals. Miraculously simple to use, they can be devilishly difficult to write. In this installment of AOP@Work series, Wes Isberg weaves a tale about 30 serious contenders in a world not too far from your own; along the way, you'll learn how to use and write library aspects and how to deliver solutions to believers and nonbelievers alike.

Share:

Wes Isberg (wesisberg@yahoo.com), Consultant

Wes Isberg is a consultant and a committer on the Eclipse AspectJ project. He was on the AspectJ team at Xerox PARC, worked at Lutris Technologies on its open-source Enhydra J2EE application server, and learned the Java language starting with JDK 1.1.2 while at Sun's JavaSoft division. Contact him at wesisberg@yahoo.com.



17 January 2006

Help!

A damsel in distress runs breathlessly to you: "Help! In test everything was fine, but we deployed the system and it just stops. No exceptions, no nothing. No one knows what to do. World peace is at stake!"

Without a word, you reach into your bag and pull out two jars. "Try this:"

java -javaagent:aspectjweaver.jar -classpath "vmErrorAspect.jar:${CLASSPATH}" ..

A minute later, out pops a stack trace. Someone somewhere was logging all Errors, including OutOfMemoryErrors, and continuing. Thank heavens for your aspect, RethrowVMError!


From magical jars to concrete aspects

About this series

The AOP@Work series is intended for developers who have some background in aspect-oriented programming and want to expand or deepen what they know. As with most developerWorks articles, the series is highly practical: you can expect to come away from every article with new knowledge that you can put immediately to use.

Each of the authors contributing to the series has been selected for his leadership or expertise in aspect-oriented programming. Many of the authors are contributors to the projects or tools covered in the series. Each article is subjected to a peer review to ensure the fairness and accuracy of the views expressed.

Please contact the authors individually with comments or questions about their articles. To comment on the series as a whole, you may contact series lead Nicholas Lesiecki. See Resources for more background on AOP.

There's no magic potion in those two jars: merely advice in a library aspect, deployed using load-time weaving. The library aspect RethrowVMError runs advice before any overeager error handlers can get to it, preventing them from hiding VMErrors. AspectJ 5 has new language features that make it easier to write library aspects and new deployment options that make it easier to use concrete aspects. Together, these can bring AOP to a broad new class of less-experienced users (using less tolerant build or deployment processes) -- but only if the library aspects are well tailored. In this article, I'll enlist you, dear reader, as an expert in writing library aspects, or at least teach you enough to ask the right questions and deploy simple library aspect solutions based on the answers.

RethrowVMError was a simple but powerful solution, easy to both understand and use. You can expect other library aspects to be more mixed in their power, understandability, and utility. What makes a good one? Success in delivering library aspects comes, I believe, from tailoring the solution to users' needs and skills, rather than trying to make the most powerful or most reusable solution. In this article, you'll learn how aspect design flows from assessing users by following a simple story. As protagonist, it will be your heroic duty to write library aspects for end users ranging from simple XML deployers to Java™ and AspectJ programmers. Along the way, you'll also train some wannabe AOP experts and convince a skeptical manager to adopt AOP. While you might be looking out for the big win, in the end you'll see that making each small step stick is the key to developing everyone's confidence in AOP.


Reusable aspects in AspectJ 5

Tiny or tinny?

Using a new language has its trade-offs, no matter how small the language is. Industry talk of late casts the proliferation of XML configuration files and LAMP components as a multitude of languages that help with specific tasks but raise the bar for developer skills. On a smaller scale, aspects build on expectations about program naming, structures, or protocols, offering various ways that programs can more or less explicitly "opt-in" using code conventions, configuration, tag interfaces, annotations, etc. The usability of a library aspect depends on whether its "tiny language" makes sense to users over time. As you write libraries, be alert to these trade-offs and be sure your language isn't tinny!

AspectJ 5 makes it easier than ever to write reusable aspects. First, it supports Java 5 language features not only on the pure-Java side, but also in the AspectJ language itself, with new forms for extending aspects and writing pointcuts. Java 5 annotations enable pure-Java developers to join the party, and even call the shots, without ever touching an aspect. Meanwhile, generic types add safety and new ways to specialize types, especially for users of new parameterized library aspects. The aspects in the article demonstrate all these features.

Second, AspectJ 5 makes it easier to write and deploy aspects without using the AspectJ compiler. For writing, it supports aspects written as annotations in pure-Java code, like AspectWerkz. These annotation-style aspects can be compiled by javac and woven later with the AspectJ weaver. For deploying, the new load-time weaver supports an XML configuration file META-INF/aop.xml that allows you to declare concrete pointcuts of an abstract library aspect in a concrete aspect. The first wave of library aspects to reach new shores will likely take this form because users do not need to know anything about AspectJ except for the minimal XML edits necessary to deploy the library.

With these new ways to deploy AspectJ libraries, users only have to know what they need to know to deploy the aspect. As load-time weaving minimizes the impact on development and build processes, aspect libraries can minimize the expertise required -- but only if library developers can meet the challenge of writing a robust aspect that works with minimal specification by deployers.


Let the games begin!

Are you up to the Library Aspects Challenge? You'd better don your armor, because your damsel in distress, Dee, works with a number of other fair ladies who now clamor for your help. Beginners like Erin and Faye mostly just want a solution. Mid-level developers Gail and Holly also want to know the details. And experts Irene, Jodi, Kelli, Liz, and Mary all prefer to build their own solutions. All are tackling hard problems. You go, girls!

Arnold, Buddy, and Connor also work with Dee, but they would rather solve other people's problems than their own. Having seen your quick work with RethrowVMError, they're eager to be heroes. They're also quick to make generalizations and defend their territories. Arnold is astonished at the difference pointcuts make, Buddy can't believe the power of annotations and mixin interfaces (but most of all wants better code), and Connor contemplates combining library aspects. You're expected to not only find solutions with the damsels, but also teach the lads a thing or two about aspects. Slow down, boys!

Zed is the one in charge, and he always has the last word. Zed hates to change the development process or invest in anything that requires a lot of expertise, but he isn't afraid to say yes when the time is right. To see how quickly developers can learn to write library aspects, Zed sends the lads to shadow you and sometimes lends a hand. If the solutions satisfy the ladies and the lads learn to write aspects, Zed is pretty likely to adopt aspect libraries in general. You'll be judged on whether you make both the solutions and the training work.

You have about 30 aspects in your quiver -- grab them now if you like! You'll find a summary of the aspects at the end of the article, in " The library aspects crew."


Erin eyeballs code using declare-error

Erin is responsible for code reviews, so Dee told her about not handling VMErrors. Because it's possible to absorb a VMError without ever using the word VMError, Erin would have few good ways to enforce this without aspects. With Zed's blessing, she huddles with you about the many other things she'd like to check for without having to pore over code. After assessing her needs and skills (like many in her position, she can't really write code herself), you show her how to write basic "within" pointcuts to specify the types affected and give her a bundle of four aspects. Each of the aspects in Table 1 signals an error at weave-time if some rule has been violated:

Table 1. Aspects for error checking
InstanceFieldNamingprohibits instance field names not starting with "f"
NoCallsIntoTestFromOutsideprohibits references from production packages into test packages
UtilityClassEnforcedprohibits construction of a utility class
NoSystemOutprohibits use of System.err or System.out
GetterSetterprohibits field-read outside initialization or getter method, and field-write outside initialization or setter method

To deploy these aspects, Erin writes some concrete aspects that all look something like the code snippet in Listing 1. You were able to quickly teach her to specify the types of interest, those in or below the com.magickingdom package:

Listing 1. withinTypes
aspect CompanyGS extends GetterSetter { 
  protected pointcut withinTypes(): within(com.magickingdom..*); 
}

She can do the same thing in AspectJ 5 with aop.xml, as shown in Listing 2:

Listing 2. Declaring concrete aspects in aop.xml
<concrete-aspect 
    name="com.magickingdom.CompanyGS"
    extends="com.isberg.articles.aop7.invariants.GetterSetter" 
    >
    <pointcut 
        name="withinTypes" 
        expression="within(org.magickingdom..*)"
        />
</concrete-aspect>

If the errors are emitted to the command-line, they look like ordinary compiler errors, except there is an additional reference back to the declare statement that defined the error, as shown in Listing 3:

Listing 3. Declare-error messages
C:\article\testsrc\com\isberg\articles\aop7\invariants\GetterSetterDemo.java:28 
  [error] non-public field-set outside constructor or setter method
i++; 
^^^^
  field-set(int com.isberg.articles.aop7.invariants.GetterSetterDemo$C.i)
  see also: C:\article\src\com\isberg\articles\aop7\invariants\GetterSetter.aj:24

The AspectJ Development Tools for Eclipse (AJDT) makes Erin's job even easier. The errors and warnings are listed along with other compiler errors and warnings, as shown in Figure 1:

Figure 1. Declared errors listed with compiler errors
Figure 1. Declared errors listed with compiler errors

At the offending code, there is a gutter marker with a context-menu navigation item, so Erin can jump back to the error declaration, as shown in Figure 2:

Figure 2. Back reference from code to error declaration
Figure 2. Back reference from code to error declaration

Arnold caught by the GSetter aspect

Erin's aspects pick up a number of violations in Arnold's code. With AspectJ's backward and forward links, Erin can choose to correct the code or adjust the error declaration. On inspection, the errors are mostly volatile fields, which shouldn't be accessed via setters. Because Arnold loves pointcuts, you teach him how to ignore volatile fields, as shown in Listing 4:

Listing 4. Ignoring volatile fields
aspect CompanyGS extends GetterSetter { 
  protected pointcut withinTypes(): within(com.magickingdom..*) 
    && !get(volatile * *) && !set(volatile * *);
}

Arnold and Erin are happy, but Zed points out that Arnold had to understand the underlying pointcut to make the change. Is this always doable? Looking at the other aspects, Zed asks, "Who understands this pointcut from NoCallsIntoTestFromOutside?" shown in Listing 5:

Listing 5. Avoid references to test package
pointcut referToTestPackage():
    call(* *..test..*.*(..)) || call(*..test..*.new(..))
    || get(* *..test..*.*) || set(* *..test..*.*)
    || get(*..test..* *) || set(*..test..* *)
    || (staticinitialization(!*..test..*)
       && staticinitialization(*..test..*+))
    || call(* *(*..test..*, ..))
    || call(* *(*, *..test..*, ..))
    || call(* *(*, *, *..test..*))
    || call(* *(.., *..test..*))
    || execution(* *(*..test..*, ..))
    || execution(* *(*, *..test..*, ..))
    || execution(* *(*, *, *..test..*, ..))
    || execution(* *(.., *..test..*))
    ;

Arnold tries to explain, but Erin gets a glazed look. Point made: There is a risk in assuming that library aspect deployers only need to know a little bit. Zed asks whether declare-error can pick out a class where a method wasn't implemented. You have to admit that AspectJ can only check whether each join point shadow is valid. It can't find those that don't exist or make general assertions about program structure. You recommend JQuery for those, and JDepends for dependency-checking. Given these caveats, Zed says that because the aspects are optional (not required to compile the program), they are okay to use at development time. To do serious static checking, however, Erin should probably get something more expressive.


Faye's frustration helps avoid boilerplate code

Faye is already frustrated by complying with Erin's demands at code review, and the prospect of more static checking sends her to you for help. She is responsible for a number of best practices that involve a lot of boilerplate code. After a brief discussion, you give her three abstract aspects to work with:

Table 2. Aspects for boilerplate code
EqualsBoilerplatehandles nulls consistently before any equals(Object)
NoNullParametersthrows an exception when public methods passed a null parameter
TrimInputStreamReadtrims any read(..) call to the available bytes

Faye deploys concrete subaspects as Erin did, and now both Erin and Faye are happy. However, Zed is worried that the boilerplate aspects are to be deployed in production code (even though they are optional and the program would compile without them). Buddy chimes in, saying EqualsBoilerplate is a better solution than nothing. It avoids a number of NullPointerExceptions by checking whether the call target is null before the call has been made. Because that check would have to be done everywhere equals(..) is called, Zed says your aspects can stay until something goes wrong with them.


Gail gathers exceptional logs

Gail takes copious notes, which is why she's been too busy to join these discussions. Zed has asked her to come up with a solution for logging exceptions. She knows her way around a code block and would like to be doing other things. After seeing what she needs, you start her with some simple aspects for logging, listed in Table 3:

Table 3. Simple logging aspects
SystemStreamsToLogredirects system stream calls to a logger
ObserveThrownlogs any exceptions thrown unless ignored
ObserveThrownContextlike ObserveThrown, but with context from the join point

Gail can adapt the aspects using both the pointcut (to Arnold's delight) and normal Java method overrides (of which Zed approves). Unlike the libraries requiring only simple pointcuts as configuration, ObserveThrown is specialized by overriding methods observeException(Throwable), getLogLevel(Throwable), and ignoreException(Throwable). By default, the aspect uses its own logger associated with the pointcut, which makes more sense to Gail than per-class loggers. Gail can understand the library aspect in part because it mostly just delegates to methods in the superclass ThrownObserver, as shown in Listing 6:

Listing 6. Observe the exception
public abstract aspect ObserveThrown extends ThrownObserver {
  
  abstract protected pointcut observe();
  
  /** Observe exception */
  after() throwing (Throwable thrown) : observe() {
    // skip if ignored or registered
    if (observingThrown(thrown)) {
      // log or ??
      observeException(thrown);
      // register to avoid duplicate calls
      registerThrown(thrown);
    }
  }
}

After looking at the new logs, Zed likes the consistency but objects that only the stack trace is being logged. You recommend the other library aspect, ObserveThrownContext, which allows them to override getPerJoinPointMessage(Throwable, JoinPoint). It's not the default in ObserveThrown because reflective access to the join point takes time and space -- probably not a concern when throwing an exception, but it should be avoided for continuous tracing.

Gail inspects ObserveThrownContext. It differs in grabbing the join point context for use in the log message, as shown in Listing 7:

Listing 7. Adding join point context
after() throwing (Throwable thrown) : observe() {
  if (observingThrown(thrown)) {
    // log or ??, including join point context
    observeException(thrown, thisJoinPoint);
    registerThrown(thrown);
  }
}

Connor considers the class hierarchy

Connor likes that most of the implementation for ObserveThrownContext and ObserveThrown is in a common superclass, ThrownObserver. That made for small aspects Gail could understand.

However, Connor wonders whether ObserveThrownContext could extend ObserveThrown or vice-versa. You explain that concrete aspects can only extend abstract ones. Moreover, there is no good way to override advice. Sometimes you can refactor the advice out into a method, but in this case, the special form thisJoinPoint is required, so it can't be. Zed concludes that in practice, you can't expect more than one or two layers of superaspects if they have advice.

Thankfully, Gail distracts Zed from drawing dangerous conclusions. She points out that this set of aspects does most of the general-purpose logging, leaving only specific logging in places where the information is not available at the join point. With aspects, logging is much more manageable than the pervasive changes she was burdened with before.


Holly holds on to caches

Holly is a pack rat who puts each thing in its place, knowing it could be useful someday. She's also a pretty good Java programmer, so Zed asked her to experiment with caching various things to see the performance impact. After talking with her, you prescribe three aspects for caching, as shown in Table 4:

Table 4. Aspects for caching
CacheToStringsave result of toString()
CacheMethodResultmap any result by key
CachedItemsave result, targeting exactly one thing

Freedom of association

These caching aspects differ in how the value is associated with the cache: using some combination of pointcuts, aspect instantiation, and maps.

CacheToString, like the earlier aspects, requires that Holly write a scope pointcut, something like within(com.magickingdom..*). Buddy points out that CacheToString is a perthis aspect, so one instance of the aspect is associated with each instance of any class implementing toString(). The cache value is stored in the aspect itself, and the aspect is committed to working only with toString() method-execution.

CacheMethodResult, by contrast, is instantiated as a singleton aspect that handles any method returning a given type. Because CacheMethodResult's subaspects are singletons working with many cached values, each aspect uses a map to associate the cached value with a given join point. Design-wise, any caching aspect involves a trade-off between putting logic into the key and putting it into the pointcut and aspect instantiation. CacheMethodResult enables the subaspect to override both the pointcut and the method that creates a key for the result, so Holly can make the trade-off with her program in mind. In one situation, the pointcut might pick out only one static method, which means only the arguments are relevant when making the key. Another subaspect pointcut might pick out a number of instance methods in different objects, making the target object and method signature also relevant for the key.

Holly likes the generality of CacheMethodResult when she is just experimenting with caching, but she prefers the tighter association of CacheToString using pointcuts. It can be expensive to construct a key each time and use the map. When debugging, it would be clear from the subaspect type which thing it was caching, whereas you would have to interpret the compound keys of CacheMethodResult to know which thing it was caching. However, CacheMethodResult limits itself to method results in scope, to make it easier for the subaspect writer to write a pointcut. As a result it cannot directly cache field values.

With CachedItem, Holly can cache anything on getting a field or returning a value from a method or a constructor call. But the aspect has no map, instead assuming the pointcut precisely identifies the value. Writing the wrong pointcut could result in conflating two values.

Holly likes the caching aspects, but she is dissatisfied because they all require that the caches be invalidated manually. Zed is also not happy about this. Although the system can run with or without the cache, the cache aspect can't be removed as long as something is dependent on it to clear the cache.


Serene Irene implements idempotent methods

Irene doesn't like change, and she doesn't like to fight about it. Like Holly, she's looking for performance gains. She would like you to help her get around idempotent methods, which are methods that have no effect if you use them more than once (for example, when opening an open resource or closing a closed resource). You code up two aspects, both of which skip the method if it has already been run. They differ in association, like caching.

Table 5. Aspects for managing Idempotent methods
IdempotentMethodpertarget of specific method
IdempotentMethodsmap key per method

These aspects use an annotation on each method, which excites Buddy. Irene wanted to identify the method in the code as idempotent so that developers would know not to change that property. If instead she enumerated the methods in a pointcut, it's possible someone would change or rename the method. With the annotation, everyone is on notice to preserve its idempotence. Zed likes that the system can change without anyone having to update the aspect.


Holly's back, for annotation-based caching

Holly, still dissatisfied with the caching aspects, butts back in. What if cached items annotated themselves with how long they were expected to live? For example, it wouldn't hurt if a product description were five minutes stale, but an auction price should be up to the minute, if not the second. If the caching aspect could read this information, it could invalidate its own cache after the specified period.

That's doable, you say. Here are two methods tagged with the time-to-live of their results:

Listing 8. Annotations for time-to-live
@TimeToLive(300)
public String getName() // ...
 @TimeToLive(100, TimeUnit.MILLISECONDS)
public Price getPrice() // ...

Listing 8 shows the TimedCacheItem aspect using the annotation value to determine when to clear the cache:

Listing 9. Clear the cache after time-to-live
Result around(TimeToLive ttl) :  @annotation(ttl) && results() {      
    Result result;
    // long nanoBirthTime set when cached 
    if (0 != nanoBirthTime) {
      // calc time to clear cache from annotation duration
      long lifeTime = ttl.timeunit().toNanos(ttl.value());
        long deathTime = nanoBirthTime + lifeTime; 
        if (deathTime < System.nanoTime()) {
          clear();
        }
    }
    // ...

Zed is happy with this solution. If the aspect is removed, the caching goes away, but nothing else is responsible for invalidating the aspect cache. Further, the estimate of how long to cache is better left with the method developer.

Everyone seems pleased, but Connor jumps in to propose even more flexibility. It's possible to have a different time-to-live for the same value if there are two access points. For example, auction participants would want a short-lived point for their price, but system administrators could use a long-lived one for calculating summaries (since small price errors don't matter to the summaries). Doing that means summaries usually won't clear caches and slow the system down. Connor's proposal would look something like Listing 9:

Listing 10. Per-access time-to-live
@TimeToLive(100, TimeUnit.MILLISECONDS)
public Price getPrice() // ...  
@TimeToLive(5, TimeUnit.MINUTES) 
public Price getPriceForSummary() // ...

Jodi's judgment is a constant annotation

Jodi is judgmental and rigid by nature: she always wants to know whether things are the same or not. She's especially good at multithreaded programming and wishes the Java language had the C keyword const, because in multithreaded code, you didn't have to worry about accessing const functions because they don't mutate anything. To make Jodi happy, you offer the Const aspect. It signals errors whenever trying to modify fields tagged as read-only or when methods or classes marked as read-only try to access things that are not read-only. Zed likes the idea but doesn't think it will be used much. However, because they are only annotations and the aspect has only declare-error statements, the aspect is harmless.

For state that does mutate, Jodi wants to implement a version number to make it easy for a client to tell if state has changed since the last time it was read. You prescribe Versioning, a concrete aspect. Jodi does not need to write pointcuts or annotations, but instead she can declare the target type to implement IVersioned, as shown in Listing 10:

Listing 11. The IVersioned interface
public interface IVersioned {
  int getVersion();
}

The aspect Versioning takes care of the implementation, and API clients use the version number directly. Zed objects that Versioning is required to compile, so it can't be removed from the system or used in load-time weaving. Jodi says she's going to work with Holly to to see if it can be used for caching and will experiment to see if it helps avoid using locks in multithreaded programs. At a minimum, AspectJ can help with the experiments, even if the eventual implementation is directly coded.


Kelli keeps track of the state of things

Kelli is one of the expert developers, and the test department complains to her about the number of bugs related to not following protocols. To detect an invalid step in the protocol as soon as it happens, Kelli wants to keep state models for components or subsystems. She starts you out with a simple resource model: it must be opened before writing, closed after opened, and never opened or written after being closed. You offer the two aspects listed in Table 6:

Table 6. Aspects for tracking
TrackedNamesAssociate a name with each join point, submit to a pluggable tracker
TrackedMethodsExtend TrackedNames to read permitted state transitions from a file and fast-fail

TrackedNames takes the name of a join point as a transition and queries a delegate ITracker whether the transition is valid. The tracker maintains any necessary logic and state. The two available ITrackers are TrackedSequence and StateTracker.

Figure 3. Tracking class relations
Figure 3. Tracking class relations

TrackedSequence expresses valid name sequences in a regular-expression form; for example, ${open}${write}*${close}. StateTracker reads state transitions from a file, in this case something like this:

Listing 12. Resource state transitions
START
START open OPEN
OPEN write OPEN
OPEN close CLOSE

Kelli prefers TrackedMethods to TrackedNames because she's happy to define transitions as method names. She prefers StateTracker to TrackedSequence because it detects invalid transitions as they happen. Overall, she uses TrackedMethods, which extends TrackedNames, and uses StateTracker to detect faulty steps as they happen.

Buddy points out that with the key-mapping method for caching, Kelli can override getName() to remap names instead of using the join point name. Zed likes the transition table form of the file because it can also be used for things like generating a complete set of test cases. Connor says that this could be also used to not just track but also implement complex protocols -- for example, to wrap a series of resource writes in a transaction.


Liz loves juggling with concurrency

Liz worked her way through college as a juggler, and she's maintained a playful spirit that suits her research position well. Zed has her doing some experiments with concurrency, trying to make the system more responsive. Liz sent Gail to the doghouse because her newfound power to log everything is slowing the system down. They've tried using filters, but neither Liz nor Gail wants to give up information for time.

You offer SpawnTrueVoids, which redirects specified void methods to a work queue running in another thread. With Connor, Liz writes SpawnLogging, which extends SpawnTrueVoids to sequence void logging calls into another thread. Unfortunately, there are non-void methods that would need to be run in sequence, at least in the logging thread. Kelli suggests two modes for the logger: initialization (no void log calls, but some mutating calls) and logging (only void log calls, spirited away to the queue in the separate thread). However, Liz would like to preserve the ability to configure loggers at run time via JMX.

Zed concludes that concurrency is hard enough when it is explicit; making it work for oblivious clients would be even harder. However, it should be feasible to use this at development time when doing extra tracing for debug purposes, and Liz and Kelli can experiment with more solutions in the future.

Parallel method enforcement

On the subject of experiments, Liz has another idea. She would like to try a different way of refactoring for concurrency, but gets tired of the boilerplate involved in wrapping code and invoking threads. What about using an annotation to declare a method "parallel"? All the code in the method could be executed concurrently.

Everyone works on this one. In the end, ParallelMethodImpl combines a number of familiar features and some new ones. Like TimedCachedItem, it uses an annotation (ParallelMethod) to identify parallel methods.

As for state-association, for each method-execution of the parallel method ParallelMethodImpl should keep a list of Futures, representing the possibly-future results of the spawned method-calls. Because this set of futures is specific to a given invocation of the parallel method, one ParallelMethodImpl is instantiated for each control flow of a parallel method execution, as shown in Listing 12:

Listing 13. ParallelMethodImpl instantiation
public aspect ParallelMethodImpl percflow(execution(@ParallelMethod * *(..))) {

Implementing parallel methods

The advice itself is fairly simple: guard and invoke. It first checks for an executor (the Java 5 facility for running a code in a thread). If an executor is not available, the advice proceeds synchronously. Otherwise it creates and runs the Futures.

Listing 14. Implementing parallel methods
void around() : withincode(@ParallelMethod * *(..)) && call(void *(..)) {
  Executor executor = getExecutor();
  if (null == executor) {
    proceed();
  } else {
    FutureTask<Object> future = new FutureTask<Object>(
      new Callable() {
        public Object call() {
          proceed();
          return DONE;
        }});
    futures.add(future);
    executor.execute(future);
  }
}

Connor discusses the mismatch between instantiation and configuration. For instantiation, there is one aspect per control-flow of the method. For configuration, one ExecutorService should work for all instances of the aspect. Liz solved this as she would in the Java language: A static method in ParallelMethodImpl takes a factory used by aspect instances to get an ExecutorService upon creation. Using the factory method enables the aspect instances to share state across instances.

Zed likes this as a development-time aspect that makes it easier for Liz to experiment. It's pretty clear from the method annotation what's happening, but it doesn't require wrapping code up in anonymous Runnables. Well done, team!


Mary moves mountains as an Observer

Mary notices whenever something needs doing. She wants an aspect that implements the classic Observer protocol. There are a number of ways to implement it in AspectJ, but you offer SubjectObserver, which parameterizes the participating types. Each subaspect represents a given relationship, so two components may be in multiple subject-observer relations with each other without any API collisions. Defining a relationship is fairly easy, as shown in Listing 14:

Listing 15. Declaring a Subject-Observer relationship
static aspect A extends SubjectObserver<S,O> {
  protected pointcut changing() : execution(void S.go());
  protected void updateObserver(S s, O o) {
    o.going(s);
  }
}

To address Zed's concern about backing out the code (and to make the aspect easier to test), you put most of the functionality in the library aspect superclass, AbstractSubjectObserver. Doing that makes the library as small as the concrete aspect:

Listing 16. Minimal library aspect
public abstract aspect SubjectObserver<Subject, Observer> 
  extends AbstractSubjectObserver<Subject, Observer> {   
    protected abstract pointcut changing();    
    after(Subject subject) returning : target(subject) && changing() {
      subjectChanged(subject);
    }
}

When clients register observers, they can use references to AbstractSubjectObserver to avoid depending directly on the aspect (though they are using an aspect, they don't need to know that!). If the aspect is taken out, the subjectChanged(..) calls must be made directly, but clients don't need to be updated.

Zed likes the solution enough to let Mary experiment with it for a while as required test code. If there are no surprises, it will be approved for production use.


Zed calls for discussion

With all your aspects out of the quiver, the question is presented: how far will Zed go in permitting aspects to become part of the team's everyday deployment? Zed asks Arnold, Buddy, and Connor to present what they've learned to everyone, so you can vet the ideas for completeness and correctness before they start writing aspects without your guidance.


Arnold on pointcuts

Arnold, already interested in pointcuts, took a special interest in declare error and declare warning statements after his code was flagged by Erin's code-review aspects. What surprised Arnold (but what makes perfect sense in retrospect) is that you used this mechanism to program library aspects defensively, flagging errors in the subaspect pointcuts. For example, parallel methods may only contain method-calls that return void, as shown in Listing 16:

Listing 17. Parallel method enforcement
declare error : withincode(@ParallelMethod * *(..)) && !call(void *(..))
  : "Parallel methods contain only void method-calls";

As another example, CacheMethodResult assumes the pointcut only picks out method-call or method-execution join points, so there is a warning if any unpermitted join point is specified:

Listing 18. Error on incorrect pointcut declaration
declare warning : targetPointcut() && !permittedPointcuts() 
    : "targetPointcut() restricted to permittedPointcuts()";

What does CacheMethodResult permit? A method call or execution returning the specified type, as shown in Listing 18:

Listing 19. Permitted results
/** method-call or -execution returning Result (+: covariant ok) */
pointcut permittedPointcuts() : 
  execution(Result+ *(..)) || call(Result+ *(..));

Remember, Result in this case is a type parameter. If the concrete subaspect specified String as the type, then it would permit only method signatures with a return type of String.

Similarly, IdempotentMethod declares errors when the pointcut picks out something other than a method execution, when the method does not return void, or when it takes arguments. It uses a pointcut to specify the join points. By contrast, IdempotentMethods uses an annotation that can apply only to methods, so it need only warn when the annotation is incorrectly placed on methods that return non-void values or that take parameters -- correcting the misplacement of annotations. (Irene thinks that could be useful just to validate annotations.) Arnold gets the point: Whenever you can, provide weave-time feedback to the deployer about mistakes, rather than having the aspect simply fail at run time.

You add that some of this feedback comes free with advice. Whenever advice declares that it throws an exception, the AspectJ tools will signal an error if any join point that might be advised is not permitted to throw that exception. Similarly, if the result of around advice can't be returned by the join point advised, then the tools will signal an error.

Still, most library aspects delegate at least part of the specification to the deployer -- by composing with pointcuts defined in the concrete aspect or targeting annotations, interfaces, types, and even member naming conventions. Not all of this can be checked at weave time, so you have to program defensively, giving up as little control as necessary for the deployer to do their job. Most often, this means using template pointcuts. Like a template method, template pointcuts are composed of parts, some of which are written by the subaspect deployer to tailor the aspect to the program at hand. Two popular template pointcuts are the Scope pattern and the Trifecta pattern.

Patterns in template pointcuts

Arnold says he noticed your use of the Scope pattern. Many of your simple aspects specified the kind of the join point in the library aspect, but allowed the deployer to specify the types or methods of interest using within or withincode pointcuts. Buddy points out that there's nothing preventing the subaspect user from using forms other than within(..); indeed, Arnold's volatile exception was formed using get(..) and set(..) pointcuts. You reply that while this may not be what the superaspect writer expected, it is safe because it can only further restrict the core pointcut, not expand it.

That's one kind of error, picking out the wrong thing, but there's another type of error to watch out for. "What happens if the pointcut picks out nothing?" Zed asks. Some advice aren't meant to match all programs, so it's not necessarily an error. But because it's likely to be a mistake, the compiler gives a warning for the advice. The warning is configurable; the user can make it ignored or an error, if they know advice isn't or is supposed to run.

Holly points out the caching aspects have a context pointcut to do run time type-checking and variable binding. You say that's part of the Trifecta pointcut pattern:

Table 7. Trifecta pointcuts
coreuser-specified join points of interest
permittedspecifies the kinds of join points and any expected static context
contextspecifies dynamic tests and values

Composed together, they look as shown in Listing 19 (with caching() as the core pointcut):

Listing 20. Pointcut Trifecta
/** the pointcut composed from the user, as permitted, with context */
pointcut results() : caching() && permitted() && context();

The Trifecta pattern addresses two issues: first, how to check when the deployer writes a pointcut that does more than specify scope. To do that, the library aspect writer specifies permitted join points and writes an error declaration identifying any unpermitted join points picked out by the deployer's pointcut, like this:

Listing 21. Pointcut guards
/** warn if subaspect pointcut picks out unpermitted join points */
declare warning : caching() && !permitted() : "unpermitted caching()";

Second, the Trifecta pattern segregates the statically-determinable part of the pointcut for use in an error declaration. These declarations cannot take pointcuts that use run-time checks because they are not always determinable at weave-time. Hence, declare error pointcuts cannot contain pointcuts this(..), target(..), or args(..). The Trifecta pattern breaks them out in a separate pointcut for the deployer, so the core pointcut can be checked independently. The Trifecta pattern is "perfect" not only because it breaks the pointcut into three parts, but also because there are three places where you see the parts: in the deployer's specification, in the warning/error statement, and in the advice itself.

Holly observes that in the trifecta pointcuts she's seen, the superaspect typically leaves the core pointcut as abstract to force the deployer to define it, but defines the context pointcut as empty, to enable the deployer to ignore it if it is not needed but override it as necessary. Like methods, overriding pointcuts can be mandatory or optional, depending on whether the supertype developer believes the default implementation will not lead to errors.


Buddy on annotations: tags with class!

After Arnold, Buddy is next up. He first thought of Java 5 annotations as tags, but the time-to-live example got him thinking. AspectJ 5 enables deployers to use Java 5 annotations to "opt-in" to pointcuts and even to communicate with advice. Buddy has done some reading of his own and points out that AspectJ 5 also allows developers to declare annotations in an aspect on other types and their members. So there are two questions for using annotations in aspects: (1) whether to use them instead of pointcuts or interfaces to specify things of interest and (2) whether to declare them in the subject code or in the aspect or both.

Buddy correctly notes that annotations can only help if they are retained at run time where the subject join point has a signature that can be associated with an annotation -- a type pattern, a field, or a method or constructor. (Tag interfaces are similarly limited to type subjects.)

In deciding how to use annotations with aspects, , Buddy says annotations differ from pointcuts in three respects. First, they act as a flag visible in the program source code indicating its nature. Second, they can contain state or code used in advice to control behavior (for example, in the caching example that picks out the time to live, or Sun's example with test code to run before a method). Third, the annotations can change when the code changes, without affecting the aspect -- an important consideration when using library aspects because it enables some flexibility without requiring the aspect itself be updated. The latter is especially pertinent when developers like Holly anticipate adding new pointcut subjects on a case-by-case basis.

To jump to the question of where to declare them, Buddy notes that one way to add lots of subjects on a case-by-case basis is to write another aspect that declares a set of annotations incorporating the new subjects. Everyone likes this idea, but you remind them of the language issue. Often, the annotation is part of a language for a given domain, like transactions or caching. It might belong with the affected members when developers need notice in the text of the code (for example, for idempotent methods), but when tools are the principle consumers (as with transactions), it may be better to consolidate the specification in the aspect.

Buddy says you can blend declaration styles, declaring some in the code and others in the aspect. Further, at the time the library aspect is written, developers don't have to decide on one strategy or another -- except that if they want only the aspect to declare the annotation, they can declare the annotation private to the aspect. In any case, it's a language question: not so much whether to use AspectJ or the Java language, but whether the user understands the tiny language made up by the annotation declarations as implemented by aspects (and possibly other tools).

The most powerful use of annotations is when they have state or code that can direct the behavior of the advice -- for example, to invalidate the cache after a certain time or to run test code. There are few limits to the expressive power of annotation data/code state as interpreted or invoked by the interpreter advice, especially in combination with join point state. Just be careful that the user understands what's happening!


Connor ties together types and join points

Connor was at first most concerned with integrating aspects, but then he grew uncomfortable with specifying a type of interest both in advice parameters or inter-type declarations (ITDs) and in a pointcut. If the type or the pointcut changes, you have to remember to change the parameters or ITDs, making it hard to maintain.

Nicely enough, AspectJ 5 solved some of that with generic aspects, abstract aspects with type parameters. The type parameters can be used in pointcuts and member declarations (though not in inter-type declarations, for now at least). The best example is again CacheMethodResult. This was parameterized using Result, and both the pointcut and the cache value were specified as such. When the deployer specifies the type parameter in the concrete aspect (for example, as File), then the pointcut only picks out methods that return File and the map only takes values of type File. That means the aspect is correct by construction, rather than having to check an invariant.

On the subject of combining aspects, Connor notes that some of the best library aspects are very small, especially where the pure Java code is implemented in a superclass that can be directly instantiated and tested alone. (His examples here are ThrownObserver and AbstractSubjectObserver.) Holly and Kelli are interested in combining caching and versioning. Rather than the aspects working directly together, each implements part of the solution, so that part can be used in other solutions. As with Java code, common public interfaces help parts of implementation avoid knowing too much about other parts. This is true of annotations and also of public pointcuts defined by the subjects of interest.


Zed wants the last word ...

Zed wants a summary of considerations. Here's what you produce:

  1. Are the aspects for production or development use? Reduce risk!
  2. Are they optional or required (e.g., to compile)? How necessary? What happens if it fails or is removed?
  3. What is required to specialize a library aspect?
    1. None
    2. Optional pointcut
    3. Simple within pointcut
    4. Narrowly-tailored pointcut
    5. Apply interface or annotation
      1. Declared in the target code
      2. Declared in an aspect
      3. Declared in both
    6. For annotations or interfaces, does the declaration influence not only the pointcut but also the advice?
    7. Override aspect methods
    8. Configure aspect delegates (e.g., with factories)
  4. Aspect style
    1. code-style: AspectJ language extension to Java
    2. annotation-style:Declare aspect in Java annotations.(aspect must be optional)
    3. XML-style(?): Declare concrete aspect in aop.xml (aspect must be optional, and only pointcuts may be abstract in the library aspect)
  5. Deployment issues
    1. load-time weaving (only works for optional aspects)
    2. load-time weaving platform variants (cumulative)
      1. Java 1.3: Custom class loader, based on WeavingURLClassLoader
      2. Java 1.4: AspectJ 1.2.1 aj.bat script, replace system class loader
      3. Java 5: Java 5 load-time bytecode weaver hook
  6. Understanding library aspects
    1. core components
      • pointcuts
      • advice
      • inter-type member declarations
      • inter-type parent declarations
      • inter-type error declarations
      • protected methods to override
      • configuration hooks
    2. numericity: Aspect instantiation, association, maps, and pointcuts
    3. type safety: advice constraints, mixin interfaces, AspectJ 5 parameterized types and aspects
    4. sensibility: Explicitness; tiny language?
    5. Maintainability
      1. Understandability
      2. Track program changes in aspect or program? How noticed?
      3. Detecting errors:
        • AspectJ tools errors and lint messages
        • aspect-declare error messages
        • test code
        • run-time invariant testing (fast-fail in production code)
        • (See robustness below)
  7. Robustness: does it work when the system changes?
    1. What assumptions are made in the pointcuts?
      1. Are they guarded with compile-time tests?
      2. Are they coded narrowly and defensively?
      3. Is there notice when a pointcut is not overridden? When one is?
      4. Are they coded narrowly and defensively?
      5. Scope and Trifecta patterns for composing pointcuts in abstract classes
    2. What is the effect of new subtypes in the system?
      1. Do the same pointcuts or annotations work with them?
      2. Do they comply with the same assumptions about the world? Same exception handling? Should all subtypes be treated like the supertype?

For this, Zed kisses you! Strange, but you take it as a good sign.


The library aspects crew

So ends the Library Aspects Challenge, an introduction to aspect libraries and the AspectJ 5 features that make them easier to write and deploy. Table 8 lists the aspects discussed in this article and included in the source file available for download. It specifies:

  • Whether the aspect is required or optional
  • Whether the aspect is for development or production
  • The topic area (and package)
  • What's required to specify the concrete aspect, i.e.,
    • within means a simple pointcut like within(com.magickingdom..*)
    • pointcut means some other pointcut
    • template means there is a template algorithm that permits some configuration by the subaspect or external client
    • config means there is some data-driven configuration
    • generic type parameter
    • code convention
    • ! means it is verified with a declare-error
  • A short description

Readers of the source code will find corresponding comments:

// CODE aspect opt dev topic [specification..]:     description

Those running Eclipse can set up the Java compiler task markers to pick out CODE comments as a way of quickly finding available library aspects. Grep does something similar for those outside Eclipse.

These aspects are written in most cases to exemplify a particular language feature. The AspectJ team anticipates that libraries will become generally available, either directly from us or from independent developers in the AspectJ community. You can go to the AspectJ home page to find the latest in library code, or contact me directly if you have questions. Happy coding! Er, deploying!

Table 8. The crew
Library aspects
CacheMethodResultoptprocachinggeneric pointcut!cache method result, keyed by context
CacheToStringoptprocaching{pointcut}cache toString, cleared manually
CachedItemoptprocachinggeneric pointcutcache Result producers cleared manually
Constoptdevinvariantsannotationerrors for const methods, fields, and classes
EqualsBoilerplateoptprolangwithinequals() null value boilerplate
GetterSetteroptdevinvariantswithin convention!error if get-set outside getter-setter
IdempotentMethodoptdevinvariants{pointcut} convention! {annotation}enforce and implement idempotent methods
IdempotentMethodsoptdevinvariants{pointcut} convention!idempotent methods (using annotation)
InstanceFieldNamingoptdevinvariantswithinrequire instance field names start with "f"
NoCallsIntoTestFromOutsideoptdevinvariantswithin conventionerror on non-test code reference to test code
NoNullParametersoptproinvariantswithinthrow iax on null public parameter
NoSystemOutoptdevinvariants{within}error on System.[out|err] usage
ObserveThrownoptproerrorspointcut templatelog exceptions thrown without context but avoiding duplication
ObserveThrownContextoptproerrors{pointcut}log exceptions with JoinPoint context
ParallelMethodImplnecproconcurrentannotation convention! {config}parallelize calls in method
RethrowThreadDeathoptproinvariantsnever catch ThreadDeath
RethrowVMErroroptproinvariantsnever catch VirtualMachineError+
SpawnLoggingoptproconcurrentwithinspawn void logging calls for performance
SpawnMutatedLoggingoptproconcurrentwithinredirect logging to another thread - how to delay for non-void calls?
SpawnTrueVoidsoptproconcurrentpointcutspawn void methods with no side effects
SubjectObserverreqpropatternsgeneric pointcut templatesubject-observer protocol
SystemStreamsToLogoptprologgingwithin templateredirect System.[out|err] to a logger
ThrownObserveroptproerrorspointcut templatesuperclass to log exceptions thrown avoiding duplication
TimedCachedItemoptprocachinggeneric pointcut annotationCachedItem with time-to-live annotation
TrackedMethodsoptproinvariantstemplate pointcuttrack method calls using FSM StateTracker
TrackedNamesoptproinvariantspointcut! config templateenforce FSM by name, with pluggable tracker
TrimInputStreamReadoptproiowithintrim input stream reads to available
UtilityClassEnforcedoptdevinvariantsannotationerror if utility class constructable
Versioningoptpropatternstagversion counter for IVersioned classes

Download

DescriptionNameSize
Source codej-aopwork14-source.zip128KB

Resources

Learn

Get products and technologies

  • The Eclipse AspectJ project: Delivers the command-line tools and documentation for the AspectJ language.
  • AJDT: By far the best IDE support for AspectJ.
  • JDepend: A well-worn and easy-to-use package for evaluating dependencies.
  • JQuery: An academic project for a query-based Java source code browser integrated with Eclipse and based on the TyRuBa language.
  • TyRuBa: A logic language.
  • IBM trial software: Available for download directly from developerWorks.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=101669
ArticleTitle=AOP@Work: Check out library aspects with AspectJ 5
publish-date=01172006