 | Level: Intermediate Wes Isberg (wesisberg@yahoo.com), Consultant
17 Jan 2006 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.
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 Error s, including
OutOfMemoryError s, 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
| InstanceFieldNaming | prohibits instance field names not starting with "f" | | NoCallsIntoTestFromOutside | prohibits references from production packages into test packages | | UtilityClassEnforced | prohibits construction of a utility class | | NoSystemOut | prohibits use of System.err or System.out | | GetterSetter | prohibits 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
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
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
| EqualsBoilerplate | handles nulls consistently before any equals(Object) | | NoNullParameters | throws an exception when public methods passed a null parameter | | TrimInputStreamRead | trims 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
| SystemStreamsToLog | redirects system stream calls to a logger | | ObserveThrown | logs any exceptions thrown unless ignored | | ObserveThrownContext | like 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
| CacheToString | save result of toString() | | CacheMethodResult | map any result by key | | CachedItem | save 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
| IdempotentMethod | pertarget of specific method | | IdempotentMethods | map 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
| TrackedNames | Associate a name with each join point, submit to a pluggable tracker | | TrackedMethods | Extend
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
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
| core | user-specified join points of interest | | permitted | specifies the kinds of join points and any expected static context | | context | specifies 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:
- Are the aspects for production or development use? Reduce risk!
- Are they optional or required (e.g., to compile)?
How necessary? What happens if it fails or is removed?
- What is required to specialize a library aspect?
- None
- Optional pointcut
- Simple within pointcut
- Narrowly-tailored pointcut
- Apply interface or annotation
- Declared in the target code
- Declared in an aspect
- Declared in both
- For annotations or interfaces, does the declaration
influence not only the pointcut but also the advice?
- Override aspect methods
- Configure aspect delegates (e.g., with factories)
- Aspect style
- code-style: AspectJ language extension to Java
- annotation-style:Declare aspect in Java annotations.(aspect must be optional)
- XML-style(?): Declare concrete aspect in aop.xml (aspect must be optional,
and only pointcuts may be abstract in the library aspect)
- Deployment issues
- load-time weaving (only works for optional aspects)
- load-time weaving platform variants (cumulative)
- Java 1.3: Custom class loader, based on WeavingURLClassLoader
- Java 1.4: AspectJ 1.2.1 aj.bat script, replace system class loader
- Java 5: Java 5 load-time bytecode weaver hook
- Understanding library aspects
- core components
- pointcuts
- advice
- inter-type member declarations
- inter-type parent declarations
- inter-type error declarations
- protected methods to override
- configuration hooks
- numericity: Aspect instantiation, association, maps, and
pointcuts
- type safety: advice constraints, mixin interfaces, AspectJ 5 parameterized types and aspects
- sensibility: Explicitness; tiny language?
- Maintainability
- Understandability
- Track program changes in aspect or program? How noticed?
- 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)
- Robustness: does it work when the system changes?
- What assumptions are made in the pointcuts?
- Are they guarded with compile-time tests?
- Are they coded narrowly and defensively?
- Is there notice when a pointcut is not overridden? When one
is?
- Are they coded narrowly and defensively?
- Scope and Trifecta patterns for composing pointcuts in
abstract classes
- What is the effect of new subtypes in the system?
- Do the same pointcuts or annotations work with them?
- 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| CacheMethodResult | opt | pro | caching | generic pointcut! | cache method result, keyed by context | | CacheToString | opt | pro | caching | {pointcut} | cache toString, cleared manually | | CachedItem | opt | pro | caching | generic pointcut | cache Result producers cleared manually | | Const | opt | dev | invariants | annotation | errors for const methods, fields, and classes | | EqualsBoilerplate | opt | pro | lang | within | equals() null value boilerplate | | GetterSetter | opt | dev | invariants | within convention! | error if get-set outside getter-setter | | IdempotentMethod | opt | dev | invariants | {pointcut} convention! {annotation} | enforce and implement idempotent methods | | IdempotentMethods | opt | dev | invariants | {pointcut} convention! | idempotent methods (using annotation) | | InstanceFieldNaming | opt | dev | invariants | within | require instance field names start with "f" | | NoCallsIntoTestFromOutside | opt | dev | invariants | within convention | error on non-test code reference to test code | | NoNullParameters | opt | pro | invariants | within | throw iax on null public parameter | | NoSystemOut | opt | dev | invariants | {within} | error on System.[out|err] usage | | ObserveThrown | opt | pro | errors | pointcut template | log exceptions thrown without context but avoiding duplication | | ObserveThrownContext | opt | pro | errors | {pointcut} | log exceptions with JoinPoint context | | ParallelMethodImpl | nec | pro | concurrent | annotation convention! {config} | parallelize calls in method | | RethrowThreadDeath | opt | pro | invariants | | never catch ThreadDeath | | RethrowVMError | opt | pro | invariants | | never catch VirtualMachineError+ | | SpawnLogging | opt | pro | concurrent | within | spawn void logging calls for performance | | SpawnMutatedLogging | opt | pro | concurrent | within | redirect logging to another thread - how to delay for non-void calls? | | SpawnTrueVoids | opt | pro | concurrent | pointcut | spawn void methods with no side effects | | SubjectObserver | req | pro | patterns | generic pointcut template | subject-observer protocol | | SystemStreamsToLog | opt | pro | logging | within template | redirect System.[out|err] to a logger | | ThrownObserver | opt | pro | errors | pointcut template | superclass to log exceptions thrown avoiding duplication | | TimedCachedItem | opt | pro | caching | generic pointcut annotation | CachedItem with time-to-live annotation | | TrackedMethods | opt | pro | invariants | template pointcut | track method calls using FSM StateTracker | | TrackedNames | opt | pro | invariants | pointcut! config template | enforce FSM by name, with pluggable tracker | | TrimInputStreamRead | opt | pro | io | within | trim input stream reads to available | | UtilityClassEnforced | opt | dev | invariants | annotation | error if utility class constructable | | Versioning | opt | pro | patterns | tag | version counter for IVersioned classes |
 |
Download | Description | Name | Size | Download method |
|---|
| Source code | j-aopwork14-source.zip | 128KB | HTTP |
|---|
Resources Learn
- "Introducing AspectJ 5" (Adrian Colyer, developerWorks, July 2005): An early look at what was new in AspectJ 5.
- "Design with pointcuts to avoid pattern density" (Wes Isberg, developerWorks, June 2005): Discusses the finer points of pointcuts.
-
AspectJ documentation: For more information on AspectJ, including AspectJ 5.
-
"Enhance design patterns with AspectJ, Part 2" (Nicholas Lesiecki, developerWorks, May 2005): Includes a different implementation of the Observer pattern using mixin interfaces.
-
"Performance monitoring with AspectJ, Part 2" (Ron Bodkin, developerWorks, November 2005): Ron Bodkin deploys the Glassbox Inspector using the AspectJ 5 load-time weaving support.
- AOP@Work series: For practical applications of AOP.
- The Java technology zone: Hundreds of articles about every aspect of Java programming.
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
About the author  | |  | 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.
|
Rate this page
|  |