AOP@Work: Next steps with aspects

After learning advice

Once you've taken the first plunge into aspects, you'll want to keep going and going, but it's never a good idea to travel without a map. In this article, esteemed aspect developer Ron Bodkin gives you a guided tour of the four stages of successful aspect adoption, from first experiments with tracing and testing all the way to building your own reusable aspect libraries.

Ron Bodkin (ron.bodkin@newaspects.com), Founder, New Aspects of Software

Ron Bodkin is the founder of New Aspects of Software, which provides consulting and training on application development and architectures, with an emphasis on performance management and effective uses of aspect-oriented programming. Ron previously worked for the AspectJ group at Xerox PARC, where he led the first AOP implementation projects and training for customers, and he was a founder and the CTO of C-bridge, a consultancy that delivered enterprise applications using frameworks for Java, XML, and other Internet technologies. Ron frequently speaks and presents tutorials at conferences and for customers, including presentations at Software Development, The Colorado Software Summit, TheServerSide Symposium, EclipseCon, StarWest, Software Test & Performance, OOPSLA, and AOSD. Recently, he has been working with the Glassbox Corporation to develop application performance management and analysis products using AspectJ and JMX. Ron can be reached at ron.bodkin@newaspects.com.



16 March 2006

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.

By now, you've surely heard about aspect-oriented programming. You know that aspects are handy for logging and instrumentation, and you've heard that they can be applied to more complex problems as well. It may be that you've already downloaded and written some simple aspects yourself or tried a product such as the Spring Framework that uses AOP to simplify development. But where do you go from here? What else can aspects do for you?

If you've just started out with aspects and you're wondering how to take the next step with AOP, then this article is for you. If you're excited about the big picture of AOP but unsure of how to apply it to your daily development practices or convince decision-makers in your organization to adopt it, then read on.

In this article, I present practical guidelines for taking that next step with aspects. I introduce the different stages of AOP adoption and offer examples of learning applications and guidelines for success at each stage. Along the way, I provide a survey of the AOP techniques and applications discussed in-depth in the many other articles in this series.

Stages of adoption

In Figure 1, you can see the general stages of AOP adoption. I like to look at the stages of adoption based on a learning curve. When learning any skill, it's important to attempt applications that are appropriate for your experience. Like object-oriented programming, AOP requires an investment of time and experience to reshape how you think about problems.

Inexperienced aspect users have established the common anti-pattern of writing aspects that essentially patch a program, but with no internal coherence. Early object-oriented developers pioneered similar anti-patterns (such as deep inheritance hierarchies) while attempting to use objects to resolve complex problems. Let's avoid those traps! In Figure 1, you can see the stages of successful AOP adoption:

Figure 1. Stages of AOP adoption
Figure 1. Stages of AOP adoption

Throughout the stages of adoption, a few key principles apply:

  • Adopt incrementally: Learn to use aspects a little bit at a time. Start with "development aspects" that don't put your production system at risk. Then apply them to advantage. Finally, expand from there. At each stage, it's important to build on what has already worked and to find new opportunities.
  • Reuse then create: Configuring prebuilt components is a great way to benefit from the power of aspects, just as it was a great way to benefit from the power of objects. As you gain experience, you will want to customize and ultimately create your own reusable components.
  • Invest in pleasant surprises: Provide no-cost examples of how aspects can solve the thorny problems in your system before asking colleagues or higher-ups to commit to aspects.

Naturally, as you become more experienced with AOP, you will gain the skills needed to use it for more interesting solutions, with correspondingly greater benefits. This can mean using aspects more broadly or more deeply, as illustrated in Figure 2:

Figure 2. Breadth and depth of use
diagramOfUse

In the following sections, I tour these four stages of AOP adoption and discuss each one based on skill level. At each stage, I provide example applications that can be used for learning. Note that the example code has been created using AspectJ 5.


Stage 1. Learning and experimenting

At this phase, the primary questions are "What can I do with aspects?" and "How do I make them work?" Traditionally, this has meant downloading an AOP tool and writing code, such as a simple tracing aspect. More recently, a number of useful aspect libraries have become available, so a good way to start learning is to download one and work with it. I'll look at both of these options from a beginner's perspective.


Starting out with aspects

Logging and tracing are classic learning exercises for AOP. They let you do something interesting and valuable with aspects while also giving you a glimpse of later stages of sophistication. Many books and articles have been written on learning to program with aspects, so I'll keep my examples brief. I just want to give you an idea of the type of applications that are suitable for this stage of adoption.

Aspects are a big improvement over traditional approaches to logging and tracing. It's easy to write aspects that resolve these common problems in certain cases. For more complex scenarios, you'll need more cooperation between objects and aspects, which is better to attempt at a later stage of learning. Tracing aspects can help a beginner to better understand how unfamiliar parts of a program work and get a closer look when debugging an intermittent problem. Likewise, Listing 1 shows how you might use aspects to log exceptions in your code:

Listing 1. Exception logging with aspects
public aspect LogModelExceptions {
    public pointcut modelCall() : 
        call(public * com.example.myapp.model..*(..));

    public pointcut externalModelCall() : 
        modelCall() && !within(com.example.myapp.model..*);

    after() throwing (Exception e) : externalModelCall() {
        logger.error("Error in calling the model", e);
    }

    private Logger logger = Logger.getLogger("LogModelExceptions");
}

This aspect logs an error whenever the program returns an exception from a call to the model. Another variation on this lesson would be to log the original exceptions for an older framework (like Struts) that didn't use exception chaining, so you can see the underlying causes of failures. This approach is also valuable to track exceptions in code that "swallows" exceptions (that is, catches them without handling them). You could "plug in" this aspect by compiling it into your application. For this, you would use an IDE that supports different build configurations such as Eclipse, Ant, or another build tool. You could also weave it in at load-time, perhaps to troubleshoot a complex test case that kept failing at run time.

Developers new to AOP sometimes apply aspects to trace the performance of their code, as shown in Listing 2:

Listing 2. Performance tracing with aspects
aspect TrackDataAccessPerformance {
  public static long threshold = 100L; // milliseconds

  /** call to any public method in the Hibernate API */
  public pointcut dataAccessCall() : 
      call(public * org.hibernate..*(..));

  Object around() : dataAccessCall() && MyApp.scope() {
      long start = System.currentTimeMillis();

      Object result = proceed();

      long end = System.currentTimeMillis();
      long elapsed = end - start;
      if (elapsed > threshold) {
          trackSlow(elapsed, thisJoinPointStaticPart, 
              thisEnclosingJoinPointStaticPart);
      }
      return result;
  }

  protected void trackSlow(long elapsed, StaticPart calledJP, 
    StaticPart callingJP) {
      System.out.println("Call from "+callingJP+" to "+calledJP+
          " took "+elapsed + "ms");
  }
}

public aspect MyApp {
  public pointcut scope() : within(com.example.myapp..*);
}

The TrackDataAccessPerformance aspect prints out the time for any method call that is slower than a given threshold, as well as what method was being called and where it was called from. This aspect is useful when you want to check for performance in a suite of integration tests. Plugging in this aspect with load-time weaving is a natural approach.

Enforcement aspects

Aspects are great at enforcing rules and policies in existing (and new) programs. This is a great application even in early stages of learning about aspects, especially when used in test code. Below you can see what happens to the TrackDataAccessPerformance aspect when I change just the implementation of trackSlow():

  protected void trackSlow(long elapsed, StaticPart calledJP, 
    StaticPart callingJP) {
      throw new AssertionFailedException ("Call to "+calledJP+
          " took too long: "+elapsed + "ms");
  }

Now the aspect causes tests that take too long to fail, enforcing a performance threshold requirement. This automates enforcing a policy that a certain operation should never take too long, which is more reliable than checking logs from test runs!

AspectJ in Action (see Resources) has another compelling example like this one. It contains a policy enforcement aspect that finds cases where threads have been used improperly with Swing. An enforcement aspect can really make a big difference in this kind of scenario, where you need to avoid subtle bugs with serious consequences.

You can also try using aspects to enforce a policy such as not allowing calls to one package from another. In the snippet below, I use the EnforceLayering aspect to prevent a program from calling model code from the data access layer:

aspect EnforceLayering {
    public pointcut modelCall() : 
        call(public * com.example.myapp.model..*(..));

    public pointcut inDataAccess() : 
        within(com.example.myapp.persistence..*);

    declare warning: modelCall() && inDataAccess(): 
         "Don't call the model from the data access tier";
}

With all of the above aspects, you can try out your crosscutting thinking and flush out logistical issues in your programs at the same time. After you've explored these simple starter aspects, you'll be looking for more ways to experiment while learning -- which is just where library aspects come in.


Learning with library aspects

For more on library aspects, see Wes Isberg's "Check out library aspects with AspectJ 5."

Prebuilt library aspects are relatively new on the scene, but some good collections are now available, including the Spring framework, the Glassbox Inspector, the JBoss Cache, and the GOF patterns library (see Resources to download any of these). Of course, many Spring and JBoss users already are using aspects without even realizing it, but you can learn a lot more, and especially get up to date with the latest in the AOP field, by going a little deeper. For starters, try out this simple exercise:

  1. Configure a sandbox to use a prebuilt aspect library. This might mean the relatively small project of creating an AspectJ build and weaving it into an existing project in your IDE. Or it might mean setting up a copy of Tomcat 5.5 and Java™ 5 for easy load-time weaving. Very soon, it could even mean downloading a VM with built-in AOP support, like BEA JRockIt has recently prototyped! (See Resources for Joe Shoop's sample sandbox and for information on load-time weaving support.)
  2. Run the system and see how the prebuilt aspects handle requirements like tracking performance, applying security, or managing transactions. In some cases, this means connecting with a client to view new data. In other cases, it means writing a small integration test to show that the aspect is interacting with your code as expected. Or you might even write another aspect to trace the effects of the library aspects.
  3. Configure the library for your environment. Read the documentation for how the aspects can be applied to your system. This might mean extending an abstract aspect with a concrete aspect that uses a pointcut to apply it to your system or using declare parents to add a marker interface to some of the types in your system. If you have aspects that are often configured using annotations, try using declare @annotation to capture where they should apply instead of writing annotations throughout your application.

    For example, you might try using the Spring 2.0 configuration aspects, which build on the code presented in Adrian Colyer's "Dependency injection with AspectJ and Spring." In that case, you could supply @Configurable annotations that are picked up with the AnnotationBeanConfigurerAspect. Alternately, you could just extend the beanCreation pointcut in the AbstractBeanConfigurerAspect base aspect. I also present a more advanced example of extending the Glassbox Inspector library with load-time weaving in the section below.
  4. Optionally, read some of the library code to understand how it works. You might try to make some small changes or extensions for your environment. See how you can best develop with the aspects in your environment.

Stage 2. Solving real problems

At this stage, the focus is shifting to applying aspects to solve real problems, but still in a limited fashion. The main question has evolved from "Could this be useful?" to "How can I really use it?" Other important questions are "Can I integrate this into my daily work?" and "How can I convince my colleagues to adopt it, too?"

Much of the effort in this stage involves working out how to effectively integrate aspects into your environment and your organization. I'll start with a strategy for doing this and then look at some aspects that are characteristic of this stage of adoption.


Early stages of integration

When you were learning aspects, you probably used tools to work with them effectively. When you think about applying aspects to your regular development process, one of the questions that will come up is how best to integrate them with your full suite of tools. You will probably need a strategy for many of the following tools:

  • IDEs (to refactor, build, test, debug, and run with aspects and visualize their effects)
  • API documentation (Javadocs)
  • Build and continuous-integration systems
  • Run-time environments (for example, for load-time weaving)
  • Code coverage tools
  • UML modeling or static analysis tools
  • Inside containers (especially for EJB)

For more on tools for integrating aspects into your development process, see Mik Kersten's two-part "AOP tools comparison" and Matt Chapman's "New AJDT releases ease AOP development."

When deciding how your tool suite will support AOP, it helps to think about two problems: how to support the development of aspects and how to support the development of code that is affected by aspects. You'll want to provide specialized tools for team members or projects that are creating aspects while making sure aspects are understandable to those who aren't writing them (yet). With increased project experience, this distinction generally fades -- just as it did when you were first working with objects -- but at the early integration stages, it's important.

Tools for non-aspect developers

I'll start with the latter category of tools because it's more important: You want to make it easy for developers not using aspects to do their job while becoming aware of aspects. At a minimum, you need to provide a means for non-aspect developers to build, test, and run woven code. Typically, this means adding aspects and aspect tests to the build and also adding run configurations to test and run woven code in an IDE. The best support for developing with aspects comes from using a good IDE plug-in like AJDT. For teams using Eclipse 3.0 or later or Rational Application Developer 6.0 or later, this will also be a natural option for those developing without aspects. Even if you're using a full-featured IDE plug-in like AJDT, it will likely be difficult to convert most of an application's Java projects to AspectJ projects. If nothing else, doing so makes it harder to develop Java code (for example, adding latency to incremental compilation).

I have found load-time weaving to be an invaluable tool for running inside an IDE: You can pick up the latest version of a jar that was built by Ant and run tests or entire applications with it. Load-time weaving does not require any special IDE support for building with aspects or require that all the projects are woven by the IDE's compiler. Configuring a test or an application to run with load-time weaving typically involves editing VM parameters in a launch configuration, which can be shared with others on the team. In Figure 3, I illustrate how to run a test using IntelliJ and load-time weaving. Some IDE plug-ins (notably AJDT) are adding options to simplify configuring load-time weaving, but you don't need these options to use the technique!

Configuring aspects for load-time weaving is a topic previously described in more depth in this article series. In general, you can set up your VM or application server with a weaving agent or use a weaving ClassLoader in your application. (See Resources for more on configuring aspects for load-time weaving.)

Figure 3. Running an IntelliJ test with load-time weaving
Running an IntelliJ test with load-time weaving

Visualization tools

It is valuable for developers not using aspects to be able to easily visualize the effects of them in their code, without changing the tools they use for their normal development. This typically means seeing where advice (might) apply and seeing how aspects affect types statically. A general-purpose option is to generate a Javadoc with links to the effects of aspects (e.g., using the AspectJ ajdoc tool). This yields a level of visualization without changing projects or adding IDE plug-ins. AspectJ 5 features an enhancement, so that it's now possible to generate crosscutting structure information from batch (Ant) builds and to use a separate tool (like the AspectJ ajbrowser) to see the effects.

I'm excited about the prospect of simpler IDE plug-ins that would use incremental Ant building support to provide basic visualization, even when using an IDE (or project type!) with little or no built-in AOP support. Such tools don't yet exist, however, so the next-best strategy is to teach other developers how to use tools to visualize, understand, and ultimately start to code aspects. It's also important to write aspects that have an appropriate breadth of use: Using static crosscutting AOP features like declare soft, declare parents, or inter-type declarations to change types can impose considerable demands on tool support and on the understanding of developers who aren't yet experienced with aspects. For example, it's possible to apply advice to a method entirely at load time. However, if your module makes calls to inter-type declarations on types it defines, then you must build it with an AspectJ compiler. As a project team grows more experienced with aspects, there are considerable benefits from using aspects more deeply and broadly, but good judgment is required when a team is still learning to use them.

Tools for aspect developers

It is also important to have good tool support for team members interested in developing with aspects, to let them incrementally compile, navigate, and visualize the use of aspects. For this, I recommend using a recent release of Eclipse with the AJDT plug-in. It's also important to minimize redundant infrastructure with good reuse (that is, add aspects to existing projects or source trees, but keep the same basic project structure).

In general, you'll need to configure tools like Ant, Maven, CruiseControl, or DamageControl to add in aspects, but that's typically fairly easy. Often you can call out to a helper task or macro that will let you plug in the AspectJ compiler, or you can add tasks for weaving in a separate step. Adding additional test assertions and static declare error constraint-checking to the build (for example, for integration and acceptance testing) can be a big step forward and typically involves adding one or two tasks to the system. Configuring continuous integration systems like CruiseControl involves some additional work if you want the tool to recognize the output of AspectJ compiles like that of Java compiles (the AspectJ 1.5.1 ant task will have improved log file output that greatly simplifies integration with CruiseControl).

Beyond this, some tools work well with aspects out of the box, while others require extra work to configure and/or supplement. For example, code coverage tools like Cobertura or Emma work on bytecode and provide good coverage analysis for aspects. If your team has invested in a Java source-code only coverage tool (like Clover), the best approach is to disable that tool's instrumentation of aspect code and supplement it with coverage data for aspects, using a compatible coverage tool.

Checking aspect requirements

If you're incorporating aspects into a project where many developers are not yet using them, it's extra important to be diligent about testing. You need to test that the aspects aren't breaking. In particular, you want to ensure that their expectations (e.g., of types) are preserved. Aspects commonly break as a result of refactoring, such as when a non-aspect developer renames or moves other types in the system. The Java refactoring tools won't replace the use of types in AspectJ-specific code unless the developer who ran them remembered to search for the fully qualified name of the type in files with the extension *.aj. Even if he did, the aspect code could still break if it uses a type pattern with wildcards, such as within(com.foo.service.*Service)), which won't be updated automatically if moving com.fee.service.Service to com.fee.service.api.Service.

By contrast, aspect code that uses imported types (for example, within(Service)) or fully qualified names is less likely to break when types are renamed or moved. It can also be valuable to periodically check crosscutting structure maps generated by tools like AJDT to see if refactorings have changed the scope of the effect of aspects. See Figure 4 for an example of how this tool works:

Figure 4. Crosscutting Structure Comparison in AJDT
Crosscutting Structure Comparison in AJDT

If you have aspects with a wide effect, it's particularly helpful to use other aspects to check that their expectations are preserved. In some cases, this can mean adding static checks using declare warning. Often, the conditions are more complex and it is valuable to check constraints in integration tests with another aspect. For example, suppose you are using aspects to track dirty objects in a GUI for repainting. You could write a test aspect that records the names of controls marked by the dirty-tracking aspect during any acceptance test. At the end of the test, the test aspect looks for a file containing expected results for the test being run. If there is one, the test aspect compares the actual set of dirty object names to the expected results and flags a failure if they don't match. This type of approach lets you control the amount of test coverage you want, from zero to all test cases.


Managing dependencies

When integrating aspects into a large, multimodule system, one of the big tool-related challenges is managing module dependencies. By modules, I mean build units such as Eclipse projects, Ant projects, and IntelliJ modules. As you develop aspects that work with a multimodule system, you will need to manage the creation of new dependencies originating with aspects. Circular-build dependencies can happen easily when an aspect in one module weaves into a type defined in another. Consider this simple metering aspect that tracks system usage:

aspect MeteringPolicy {
  private int Metered.usage;

  declare parents: Playable implements Metered;

  after(Metered m) returning: 
      execution(public * Playable.*(..)) && this(m) {
      m.usage++;
  }
}

public interface Metered {
...
}

If MeteringPolicy is in a different module from Playable, then its module has a logical dependency on Playable's module. Although Playable's module doesn't have a logical dependency on MeteringPolicy's, it does need to be woven by it (that is, it needs to be linked with the other module). You can generally solve this problem by building the modules in the order of logical dependencies and then weaving in a later step using load-time weaving or a separate weave task in Ant. In an IDE, this is a little trickier to handle, especially if you want to be able to visualize the effects of aspects on other modules. Today, the best practice for allowing visualization with such cross-module dependencies is to set up additional projects and then build a chain of weaving, as shown in Figure 5:

Figure 5. Configuring a multimodule system to visualize the effects of aspects
Configuring a multimodule system to visualize the effects of aspects

In the future, AJDT will hopefully support the visualization of the effects of weaving on inpath entries. You'll then be able to simply weave into other projects in a dependent-aspect project. See Resources for more on this.

A linked source module is an AJDT project that uses the Eclipse Linked Source Folder feature. This configuration creates a link from all the source folders of the original project to be woven into as linked folders that are sources in the build path. It also has the dependent-aspect module(s) on its aspectpath, and it includes all of the classpath entries of the original project. The dependent-aspect module depends on the original module, not on the linked source module. The linked source module can even be closed during quick, incremental development and reopened for when you want to do visualization.

Avoiding dependencies

Of course, the best solution to these types of dependencies is to avoid them altogether. In simple cases, you can use localized aspects that are built within a single module and have no effect on other modules. A more general strategy is to use abstract aspects that depend only on interfaces they publish and concrete aspects that are local to the modules that apply them. This is an example of the Participant Pattern as discussed in AspectJ in Action (I like to configure the participation per module, rather than per class).

It can be tempting to corral all the aspects in a system into one module, but most projects will quickly expand to depend on many of the modules in the system. This, in turn, maximizes the challenge of managing logical dependencies from aspect modules to the modules they affect. By contrast, it is preferable to put aspects in their "natural" modules. To avoid impacting non-aspect developers in the process, consider using an .aj file extension for aspects (or even an annotation syntax for aspects). Most Java development tools do not have any problem at least ignoring .aj files in the project tree. Using this strategy, you may be able to just change the nature of an Eclipse project from a Java project (to ignore the aspects) to an AspectJ project (to include them). Sometimes it is more convenient to separate aspects into different folders. If need be, it's still worth trying to use a separate source tree for aspect-enabled code in an existing module rather than creating separate modules for aspects. Moreover, there are also very good reasons to keep aspect modules separate and for them to have logical dependencies (just as with any other kind of module). The guiding principles are simply minimizing coupling between modules and maximizing cohesion within them.

If you're facing circular-logical dependencies among modules that contain aspects, you can apply the Dependency Inversion Principle just as you would in object-oriented programming: make the more abstract types depend on an interface abstraction and not on an implementation. In the above example, you might do this by moving the Playable interface to a separate module so that the MeteringPolicy module could depend on it without depending on any classes that implement Playable (and into which it is woven).


Aspects for real projects

I've given you an overview of tools and techniques to effectively integrate aspects into your shop. Now let's consider some of the areas where aspects can really shine and maybe win over reluctant team members. Using aspects to resolve everyday problems like security enforcement, performance monitoring, caching time-consuming calculations, and error handling is a great way to integrate them into your projects where they add a lot but are still quite decoupled. The examples that follow omit some of the details but preserve the integration issues that are characteristic of this second-stage use of aspects on real projects.

Security enforcement

Aspects are a great way to enforce security rules such as role-based access control. A related example is enforcing the requirement that users have a valid license when using an application. Checking for a valid license requires diligent enforcement when performing many operations. Pervasive checks for a valid license make it harder to defeat the licensing scheme. They also allow for more granular licensing, such as granting different rights to different licensees. Aspects are a natural fit for a problem like this, as you can see in Listing 3:

Listing 3. An aspect for license enforcement
private aspect LicenseEnforcement {
  pointcut startup() : 
      execution(void main(String[]));
  pointcut enforcementPoint() : 
      execution(private * *(..)) && scope();
  pointcut termination() : 
      startup();

  pointcut scope() : 
      within(com.myapp.*) || within(org.apache..*);

  before() : startup() {
      license = licenseManager.acquire();
  }

  before() : enforcementPoint() {
      if (license==null || !license.isValid()) {
           throw new IllegalStateException("License violation");
      }
  }

  after() : termination() {
      licenseManager.release(license);
  }

  public void setLicenseManager(LicenseManager mgr) { ... };
  public LicenseManager getLicenseManager() { ... };

  private License license;
  private LicenseManager licenseManager;
}

Here the aspect is responsible for acquiring a license on start up, checking for a valid license in many places, and releasing the license. In this example, license use is enforced when executing any private method in the system that is in scope. The scope includes a third-party, open source library from Apache, making it easier to integrate enforcement throughout the delivered application. You may be wondering how the license manager is initialized. This aspect is designed to be configured through dependency injection as described by Adrian Colyer's "Dependency injection with AspectJ and Spring." Of course, the aspect could also just look up configuration for the license manager or even construct one directly.

Error handling

Aspects are incredibly useful to improve the reliability and consistency of how applications respond to error conditions. Examples include converting exception types, handling exceptions at common points such as in Web application controllers, logging exceptions when they first occur, creating summary reports on errors, and isolating errors in auxiliary subsystems so that they can't affect core systems. (I described the last two examples in my previous article in this series; see Resources.)

The steps that follow show you how to create a simple yet comprehensive end-to-end exception handling policy for a Web application. I convert exceptions thrown from the business model to be instances of an unchecked ModelException. The ModelException holds additional context information about the objects and parameters executed, not just a stack trace. I show you how to log exceptions exactly once and how to handle errors by directing the user to the right kind of information, including relevant data to improve and expedite user problem resolution. Best of all, this strategy doesn't require adherence to a pattern-dense framework: it works with custom application code or your favorite third party library code.

End-to-end exception handling

The first aspect, shown in Listing 4, is responsible for converting data access and service exceptions from the model (business logic) layer into meaningful exceptions and for capturing context information about what the code in question has been doing:

Listing 4. Converting exceptions within a model
/** Error handling for methods within the model */
aspect ModelErrorConversion {
  /** execution of any method in the model */
  pointcut modelExec() : 
      execution(* model..*(..));

  // convert exception types
  after() throwing (HibernateException e): modelExec() {
      convertException(e, thisJoinPoint);
  }
  after() throwing (ServiceException e): modelExec() {
      convertException(e, thisJoinPoint);
  }
  after() throwing (SOAPException e): modelExec() {
      convertException(e, thisJoinPoint);
  }
  after() throwing (SOAPFaultException e): modelExec() {
      convertException(e, thisJoinPoint);
  }

  // soften the checked exceptions
  declare soft: ServiceException: modelExec(); 
  declare soft: SOAPException: modelExec(); 

  /** Converts exceptions to model exceptions, storing context */
  private void convertException(Exception e, JoinPoint jp) {
      ModelException me = new ModelException(e);
      me.setCurrent(jp.getThis());
      me.setArgs(jp.getArgs());
      // ModelException extends RuntimeException, so this is unchecked
      throw me;
  }
}

The ModelErrorConversion aspect ensures that exceptions thrown by the Hibernate persistence engine or from a JAX RPC call are converted to a specific type of run-time exception when exiting a method in any class in the model package. It also ensures that the SOAP exceptions are softened: they are converted from checked exceptions that must be declared or caught in Java code into run-time exceptions that need not be. In addition, this error-handling logic captures context information on the exception: what object was executing and what arguments were passed to the method. I can build on this with another aspect that performs error handling on calls to the model from outside, as shown in Listing 5:

Listing 5. Handling exceptions on entry to the model
/** Error handling for calls from outside into the model */
aspect ModelErrorHandling {
  /** Entries to the model: calls from outside */
  pointcut modelEntry() : 
      call(* model..*(..)) && !within(model..*);

  /**
   * Record information about requests that entered the model from outside 
   * where an object called them
   **/
  after(Object caller) throwing (ModelException e) : 
    modelEntry() && this(caller) {
      handleModelError(e, caller, thisJoinPointStaticPart);
  }

  /** 
   * Record information about requests that entered the model from outside
   * where no object was in context when they were called, e.g., in a
   * static method
   */
  after() throwing (ModelException e) : 
    modelEntry() && !this(*) {
      handleModelError(e, null, thisJoinPointStaticPart);
  }

  private void handleModelError(ModelException e, Object caller, StaticPart staticPart) {
      if (e.getInitialMethod() == null) {
          e.setInitialCaller(caller);
          e.setInitialMethod(staticPart.getSignature().getName());
      }
  }
}

At this tier, I'm recording an extra piece of context for exceptions, namely what object was executing at the entry to the model. I can then use this information in the Web tier to provide helpful information to the user, as shown in Listing 6:

Listing 6. An aspect for user interface error handling
aspect UIErrorHandling {
  pointcut actionMethod(ActionMapping mapping, 
    HttpServletRequest request) : 
      execution(ActionForward Action.execute(..)) && 
      args(mapping, *, request, *) && within(com.example..*);

  ActionForward around(ActionMapping mapping, 
    HttpServletRequest request) :
    actionMethod(mapping, request) {
      try {
          return proceed(mapping, request);
      } catch (NoRemoveInUseException e) { 
          addError("error.noremoveinuse", e.getCurrent());
          return mapping.getInputForward();
      } catch (ModelException e) {
          return handleError(e, mapping, request);
      }
      catch (InvocationTargetException e) {
          ApplicationException ae = 
              new ApplicationException("populating form ",  e);
          return handleError(ae, mapping, request);
      }
      catch (Throwable t) {
          ApplicationException ae = 
              new ApplicationException("unexpected throwable ", t);
          return handleError(ae, mapping, request);
      }
  }   

  private ActionForward handleError(ApplicationException ae, 
    ActionMapping mapping, HttpServletRequest request) {
      // track information to correlate with user request
      ae.setRequest(request);
      ae.logError(getLogger()); // logs context information...
      session.setAttribute(Constants.MESSAGE_TEXT, 
          translate(e.getMessage()));
      session.setAttribute(Constants.EXCEPTION, e);
      return mapping.findForward(Constants.ERROR_PAGE);
  }

  private void addError(String errorId, Object current) {
      // get name and key from Entity objects
      // then add a Struts action error
  }
     
}

The UIErrorHandling aspect is responsible for ensuring that any Struts actions in the com.example package or its subpackages have errors logged and then handled. For recoverable errors (illustrated by NoRemoveInUseException), it simply presents an error to the user with relevant context data (what object was in use) and returns to the same page. For other errors, it converts them to an application exception (of which model exception is a subtype) and delegates to common error-handling logic. The error-handling logic records information about the Web request to allow the error to be correlated with the user and session. It finally redirects to an error page, storing an additional attribute with the exception that was encountered.

The stored attribute in the forwarded page makes it possible to add an HTML comment with information that a support person could use to correlate user errors with logged exceptions. Note that application exceptions are logged specially: The logError() method logs all the additional context information that's been captured, providing a "black box recorder" of information to ensure the error can be isolated, reproduced, and fixed, rather than forcing you to wonder what might have been null in the NullPointerException. In particular, it logs all relevant information such as the recorded arguments and key objects in the call tree, as well as traditional stack traces.

Extending a library aspect

Finally, let's see what happens when I want to build on an existing aspect library. In this case, I extend the Glassbox Inspector performance monitoring library to measure the performance of XML processing times in my application. (See Resources for more information about the Glassbox Inspector.)

I can do this in six steps:

  1. Read the documentation for the Glassbox Inspector on how to extend it.
  2. Create a sample IDE project.
  3. Write a simple aspect to track performance of my XML processing code (shown in Listing 7).
  4. Create a load-time weaving descriptor.
  5. Build a JAR from the aspect and the descriptor.
  6. Add the JAR to my system with load-time weaving.

Step 3 is illustrated below. In this case, my application has a DocumentProcessor interface, and I want to track the performance of any public methods that are defined in that interface, to determine whether they are slow.

Listing 7. Extending Glassbox Inspector to monitor custom code
public aspect DocumentProcessorMonitor extends AbstractResourceMonitor {
  /** apply template pointcut to monitor document processor methods */
  protected pointcut methodSignatureResourceReq() : 
      execution(public * DocumentProcessor.*(..));
}

This code hooks into the existing Glassbox Inspector framework. It defines a template pointcut methodSignatureResourceReq to pick out join points in my code that I want to monitor, namely the execution of any public method defined on the DocumentProcessor interface. This will match the methods in any implementation of the interface, but not additional methods defined on those classes (which I do not want to monitor in this case). The framework then uses the method name at this join point to group performance statistics for each method separately. Having written this small aspect, I can build a simple load-time weaving deployment META-INF/aop.xml descriptor, as shown here:

<aspectj>
    <aspects>
        <aspect name="com.myco.monitor.DocumentProcessorMonitor "/>
    </aspects>
</aspectj>

I can then package the descriptor with my compiled class file into a separate extension jar that will add to the standard Glassbox Inspector monitoring capabilities. I then deploy the jar along with the standard Glassbox Inspector JAR, and I can see data from my new monitor along with standard Glassbox monitoring data with JMX. And that's it! The AspectJ 5 load-time weaving system and the Glassbox architecture make it simple to add new monitors without repackaging or complex reconfigurations.


Stage 3. Integrating aspects into core development

At this stage of learning, there is a natural momentum into more advanced uses of aspects. Whereas the previous stages tend to focus on effective integration of technology, here the attention shifts to more effective uses of aspects. This is analogous to that ah ha! moment when you first starting thinking in terms of objects. For those who are still newcomers to aspects, my discussion here will be a bit complex, just like reading the Gang of Four would be overwhelming to someone who had just started writing objects.

The hallmark of this stage is closer collaboration between aspects and classes. A key question is how to expose domain concepts for so-called business aspects. Aspects written in earlier phases of learning tend to focus on auxiliary concerns that can be unplugged from the system and on horizontal concerns that apply across many domains (such as transaction management, caching, or tracing). Another major question at this stage is how to integrate aspects effectively into the full development life cycle, although that topic is beyond the scope of this article.

AOP expertise typically expands within teams and organizations at this stage. As you've seen in my discussion of Stage 2, organizations tend to start with a few aspect gurus who do most of the aspect coding and are primarily responsible for integrating them with other components, as well as managing their impact on other projects and developers. As awareness of AOP grows within the organization, however, other developers naturally learn more and start writing their own aspects.

I can't provide detailed example applications for the integration stage because the requirements and designs invariably would need to be scaled up and would be worthy of an article on their own. So instead, I'll discuss the highlights of three real-world aspect implementations that are typical of this stage of experience: fine-grained security, managing persistent object relationships, and implementing user interface macros.

Fine-grained authorization

As I showed in the previous section, basic security rules enforcement is a natural use for aspects. Moreover, many applications must enforce more advanced data-level security, whereby access to object instances or even their fields is controlled by rules. Examples of this include "only an employee's manager or an HR administrator for their group can view their sensitive personal data" or "only managers assigned to a group can trade on the group's behalf." Consider an example that enforces the first rule, as shown in Figure 6 and Listing 8:

Figure 6. Sample human resources domain model
Sample human resources domain model
Listing 8. A fine-grained security aspect
public aspect SensitiveDataAuthorization {
  /** 
   * Matches sensitive read operations on employee data, namely reading
   * sensitive fields
   */
  pointcut readSensitiveEmployeeData(Employee employee):
      target(employee) && (
      get(* salary) || get(* address) || get(* bonus));

  /** 
   * Matches the set up of a security context, in this case for JAAS
   */
  pointcut securityContext(Subject subject, Action action):
      cflow(execution(* Subject.doAs*(Subject, Action, ..)) &&
      args(subject, action, ..));

  before(Subject subject, Employee employee) :
    readSensitiveEmployeeData(employee) && 
    securityContext(subject, *) {
      policy.checkAccess(subject, employee);
  }

  /** 
   * Matches sensitive read operations on regulation data, namely calling
   * get methods or calculating tax on it.
   */
  pointcut sensitiveRegOp(EmployeeRegulation reg):
      this(reg) && (
      execution(* get*()) || execution(* calcTax()));

  before(Subject subject, EmployeeRegulation reg) :
    sensitiveRegOp(reg) && securityContext(subject, *) {
      Employee employee = reg.getEmployee();
      policy.checkAccess(subject, employee);
   }

   public void setPolicy(EmployeeAccessPolicy policy) { ... }
   public EmployeeAccessPolicy getPolicy() { ... }
   protected EmployeeAccessPolicy policy;
}

public class EmployeeAccessPolicyImp implements EmployeeAccessPolicy {
  /** 
   * Checks for valid access to data.
   * @throws SecurityException if not valid
   */
  public void checkAccess(Employee employee, Worker worker) 
    throws SecurityException {
      Employee caller = 
          Employee.getEmployee(worker.getSubject());
      if (caller==null || !employee.reportsTo(caller))) {
          // record attempted security violation
          throw new SecurityException("...");
      }
      // log successful data access to audit trail
  }

}

In Listing 8, the SensitiveDataAuthorization aspect has a first advice on readSensitiveEmplData() that secures reads of sensitive data fields defined directly on the employee class. The advice advises such reads whenever they are made in a security context (in this case, when the user is authenticated through JAAS). The advice delegates to the checkAccess() method in a helper class, EmployeeAccessPolicy, which checks the relationship between the acting subject (typically a user) and the employee whose data is being accessed. If the required relationship is not present, then it throws an exception and raises an alert. Otherwise it logs the attempted access.

The second part of the aspect begins to get more interesting: here I'm securing access to employee data that's held by a composed object. In particular, the EmployeeRegulation object holds sensitive data such as government tax identifiers, tax rates, and withholding information. Naturally, this information varies depending on the jurisdiction of the employee (country, state, province, city, etc.). Executing the calculate-tax method or getting any data from any implementation of regulatory data is deemed sensitive. In this case, I need to map from the regulation data back to the aggregating object (the employee) to check access.

Effective policy enforcement

With the authorization example, you begin to see what makes aspects uniquely effective at policy enforcement: The use of join points is a powerful way to pick out and refine conditions for applying security rules. In this example, I used both field access and method calls to apply security. It's also worth noting that I defined a business rule for what accesses were allowed based on context data (what subject and what employee's data were being accessed). This is far preferable to having low-level access control lists for each object: while the latter is highly flexible, it makes it virtually impossible to enforce policy and it is often at risk from misconfiguration. With the flexibility of AOP, it is easy to enforce security at the right points and to expose the right data for automatic policy decisions based on business rules.

The authorization example also introduces the important topic of mapping business relationships into aspect code. I had to determine what employee was related to the given employee regulation object. In this case, I assumed there was a reverse association from regulatory data to the aggregating employee object. I also explicitly coded the knowledge into my aspect. In more general-purpose security aspects (and other business aspects), this becomes a more complicated problem.

As a last note, security aspects can be viewed as a special case of business policy enforcement. It is a natural step to use aspects to enforce the rule that no business transaction may be performed on a suspended account. This implies the need to track the services that may generate charges against accounts. This is similar to enforcing access control rules, but it is based on a dynamic context (whether the account has been suspended).

Persistent object relationships

At the Aspect-Oriented Software Development 2005 conference, Nicholas Lesiecki gave an industry report on how he and his colleagues at VMS used aspects to manage object relationships automatically with the Hibernate persistence engine. Nick is a seasoned aspect developer, and his report illustrated a typical stage-three integration of aspects into an existing project.

Suppose you had a domain model where a manager might manage zero to many employees and each employee had a reference to their manager. If you wanted to add an employee using strictly object-oriented code, you would need to write something like this:

employee.setManager(manager);
manager.getEmployees().add(employee);

Using the persistent relationship management aspects, you could simplify the code to just this:

manager.getEmployees().add(employee);

You could also write it the other way:

employee.setManager(manager);

In this example, aspects are used to automatically propagate changes in object relationships, ensuring that bidirectional one-to-many and many-to-many relationships remained consistent. In this way, the VMS team avoided the risk of transaction failures and the tedium of making redundant API calls.

Implementing this functionality required the VMS team to integrate closely with an existing library to provide additional functionality through aspects. The project members also had to track business relationships between objects, which they did using information that had been exposed in Hibernate about persistent relationships. This same requirement to track relationships among business objects for an aspect also came up in the fine-grained security example. I look more at this common requirement for business aspects below.

UI monitoring and macros

I'm working on a customer project where we're applying aspects to both monitoring user behavior and recording macros in a rich client GUI application. We have four primary purposes in monitoring the GUI application:

  • Better understand how users use the application (so we can improve usability)
  • Target user training of difficult concepts or functions that aren't used consistently
  • Understand different user clusters
  • Prioritize features for further development

Our aspects have to track various user interface events and have some knowledge of how controls are structured and how page navigation works. They also need to be able to track state in the application, identify calling screens for wizards or modal dialogs, and properly capture reverse-navigation events (Back buttons). In addition, the application monitors relevant system events, like external connections and any errors the user has encountered. As user interactions and system events are captured, they are recorded anonymously in a log file for subsequent batch transmission.

This use of aspects is closely related to capturing data for server-side analysis in Web applications. Because of the regular structure of Web requests, basic URL analysis is available for any Web application. Unlike tracking a simple Web site, this kind of monitoring requires integration with the specific UI structure of the application to identify what events are interesting and what views are worth tracking. The use of more complicated requests and variations in business flow can make it useful to apply aspects to monitoring user behavior in Web-based, Ajax, or rich-client applications that invoke back-end services.

The use of aspects for UI monitoring is the subject of an upcoming industry report I wrote with Jason Furlong. I will present the report at the Aspect-Oriented Software Development conference in March 2006 (see Resources).

Furthermore, having used aspects successfully for UI monitoring, we've subsequently implemented macro recording and playback in this application. These aspects record incoming UI events for mouse or keyboard actions and identify the target UI control. They then record these events along with the receiving UI views. On playback, the aspects regenerate UI events once the target current main window (screen) and control is visible. Interestingly, the UI monitoring implementation provides the key logic required by the macro aspect to track the current screen for recording and play back.


Core integration techniques

As the examples in this section have illustrated, the more advanced integration stage of development brings several new techniques into play:

Exposing business relationships

Domain-specific aspects often are concerned with the relationships among objects and business concepts in the domain. This information isn't in the "infoset" of Java classes, types, and methods. In some cases, annotations provide this information. I believe the ideal approach is to use annotations that describe domain concepts (like @BusinessTransaction or @Aggregates(1, MANY)) and to use these to derive specific business concerns (such as persistence rules). However, with the emergence of standard Java EE 1.5 annotations -- notably those from the EJB 3.0 Java Persistence API -- many projects will provide these annotations, making it attractive to use declare @annotation and/or pointcuts to identify business relationships from the annotations (for example, determining object aggregation from cascading persistent data).

For projects not using annotations to track object relationships, the metadata that's defined in persistence engines is often the only place where projects have explicitly defined business relationships (such as aggregation and cardinality of relationships). This makes it attractive to use techniques like the one that VMS used to determine object relationships from the engine's mapping data. Along these lines, I am helping Sergei Kojarski to create a library that exposes business relationship data from Hibernate for use with aspects. John Heintz is also working on making the VMS code available for reuse in the Codehaus Ajlib aspect library project. I believe these developments will make it much easier to write business aspects.

Tracking join point sequences

In many larger scale applications of aspects, it's necessary to track a series of join points to determine the state that an operation is in and gather context data. This is often an extension of cases where control flow (cflow) pointcuts are useful: When you can't rely on having the preceding join point still on the "stack" of incomplete join points, it's usually necessary to store state. You might do this in a percflow aspect (that is, in the control flow of a common ancestor that is on the stack for both join points) or with a ThreadLocal variable.

Aspect configuration

As you scale up the power of aspects and they interact with other types, configuration and testing become more of an issue. For more about aspect configuration and testing, see Adrian Colyer's "Dependency injection with AspectJ and Spring" and Nicholas Lesiecki's "Unit test your aspects."

Aspects on aspects

As aspects perform more of a role in an application, it becomes more natural to have them explicitly cooperate, such as using aspects to configure other aspects for dependency injection. In my article "Performance Monitoring with AspectJ" I offered two examples of this: aspects that isolate errors in other aspects and aspects that automatically register objects with JMX, including other aspects.


Stage 4. Sharing with others

At this final stage of developing with aspects, the emphasis shifts to building generalized, reusable mechanisms. Just as with objects, it takes general experience in working with a technology (AOP) and domain expertise to write good, reusable aspect code. Biggerstaff and Richter's Rule of Three (which implies that a designer should looked at three system before creating a reusable implementation) applies as much to aspects as it does to objects.

See Nicholas Lesiecki's "Enhance design patterns with AspectJ, Part 2" for more about using aspects and a refactored Library Observer pattern to track the use of a service.

Providing feature variations is a compelling use case for aspects at this stage of sophistication. For example, consider a commercial software vendor that is interested in offering a product line that builds on open source, while adding support for value-added services. The industry report "Large-scale AOSD for middleware" describes the motivations and experiences at IBM in this area (see Resources).

For a smaller, more concrete example, let's consider the use of metering to track the use of a service for the purpose of customer billing.

Pointcut interfaces

I'll start by looking at how to implement this requirement without using the object-oriented Observer pattern. I can do this simply by publishing a pointcut for external clients to use -- for example, an aspect that simply defines a pointcut for when a metered event (such as using a title) has occurred:

aspect MeteringPolicy {
  declare parents: Playable implements Metered;
  public pointcut meteredUse(Metered metered) :
      titleUse(metered);

  pointcut titleUse(Metered metered) :
      this(metered) && ( 
      execution(public void Playable+.play()) ||
      execution(public void Song.showLyrics()) );
}

Another part of the code can then write an aspect to respond whenever a metered use occurs (for example, to check credit, to record charges, to bill immediately, to decrement prepayment counters, or to start a timer). Unlike a traditional Observer implementation, there isn't a need to explicitly dispatch events. In general, publishing pointcuts is more flexible than traditional extensibility mechanisms that require predefined hook methods and often cumbersome means for registering and dispatching to callbacks. In a related vein, consider the variety of before and after event callbacks provided by a typical object-oriented persistence framework. For example, the EJB 3.0 Java Persistence API public draft specification uses annotations and callback methods to support the following seven life-cycle events, with two versions of each, to call back either the persisted object or a generic listener:

  • PrePersist
  • PostPersist
  • PreRemove
  • PostRemove
  • PreUpdate
  • PostUpdate
  • PostLoad

With an AOP implementation, these could be published as four pointcuts that could be combined and refined in any way desired. Note that in some cases, publishing a pointcut for external use encourages structuring code in a particular way so that a simple pointcut definition can be exposed that represents a sequence of join points in the implementation.

Feature variations

I think the most interesting facet of the metering example is the ability to vary features for different contexts. Consider the different rules that might come in to play:

  • Checking credit before use
  • Real-time billing
  • Batch billing
  • Metering before use
  • Metering per session
  • Tracking rewards
  • Metering usage by user
  • Metering usage by payment source (for example, anonymous stored value cards)
  • Enabling different marketing campaigns
  • Making offers
  • Enabling capabilities and tracking for different affiliates

I can vary the way I configure metering, the points at which I apply metering, the results after metering (including billing and possibly suspending and/or denying access) in a modular fashion. In essence, I can package the metering system and related capabilities as a set of a la carte options to be combined as desired for a specific deployment. In a scenario like this, managing all these variations in a single object-oriented codebase would be quite difficult. Even where object-oriented programming would allow for a modular implementation, it would require significant pattern density that would inhibit future extensions and improvements and would tend to be tightly coupled.


Writing reusable aspects

In many respects, building a good aspect library requires the same skills and knowledge that building a good library always does, as noted earlier. In particular, it requires domain experience, good taste, experience with the technology (aspects), and often deep systems programming knowledge (for example, to avoid memory leaks or to support good concurrency with thread safety). On top of this, however, you will need to master some new skills in order to build a good aspect library:

Several previous AOP@Work articles have discussed reusable aspects in depth, notably

Likewise, "Check out library aspects with AspectJ 5" discusses the issues that come up when writing reusable aspect libraries.

Design for extensibility

First, it's important to plan carefully how the aspect library will be configured, extended, deployed, and possibly limited. I looked in detail at several strategies for extensibility in aspect libraries in "Performance Monitoring with AspectJ, Part 2."

Design for completeness

I've learned first-hand how important it is to ensure that reusable aspects handle all the valid interactions among modules with which they interact. In particular, it is natural to write aspects that extend or leverage the functionality of other libraries, such as by monitoring performance of, managing relationships among objects persisted by, or tracking errors in the library. A reusable library that performs such tasks for another library needs to track all the valid patterns of use for that library, not just the ones that a given developer knows. In short, it imposes a need to truly master the design of the library being extended, rather than merely knowing one way to use that library.

In many cases today, using aspects to extend another library is working from a fairly inconsistent or undocumented definition of what events occur in the system life cycle. It's important to make sure the actual system states match expectations in all valid uses (especially for portability across systems) and to take into account all the API mechanisms for achieving a given goal. By contrast, when building an object-oriented library that extends some existing library, you typically need to find only one standard way of leveraging functionality. Another way at looking at this rule is that aspects give you a lot of power to handle things consistently. So it's important to understand the full range of what your aspects are doing and in what cases they should operate. Likewise, reusable aspects should have a clear strategy for versioning and requirements for the versions of collaborating libraries (just as class libraries should).

Plan for the entire life cycle

Pay careful attention to the interaction of aspects in the life cycle of a system. Initialization is always a delicate time in the life cycle of objects. When initializing aspects, it's important to think carefully about interactions with static (class) and instance (object) initialization and with other aspects, including how to handle errors properly. Aspect initialization needs to be correct in all the cases where the aspect is being initialized. Moreover, the default singleton aspects of AspectJ are initialized immediately when their classes are loaded, making it important to bootstrap any configuration mechanism before configured aspect types are loaded. I have experienced the importance of this rule especially when writing aspects to handle logging, error handling, runtime control, configuration, and automatically registering types for JMX.

Write thin

It's important to maximize the isolation of aspects from the code they are collaborating with. In many respects, this is just the old rule of avoiding coupling by minimizing dependencies and being lenient with values you accept and strict with values you provide. Writing "thin" aspects that delegate to implementation objects is a technique that I have found to be quite valuable. It's often desirable to configure aspects with dependency injection and to depend only on interfaces, which makes it easier to change implementations.

Learn to reuse differently

AOP allows for a new kind of reusable code. It enables an a la carte use of less-coupled functionality with feature variations and open extensibility. AOP libraries typically involve collaboration among aspects, classes, and interfaces. I was first excited by AOP because of the opportunity to improve on traditional object-oriented frameworks by avoiding significant coupling, large up front investments, and difficulty in incremental adoption. I believe that reuse will prove to be the most important benefit from adopting AOP.


In conclusion

In this guided tour of the four stages of successful aspect adoption, I've given you an overview of the aspect adoption process. In addition to laying out the core concerns for developers and organizations at each stage, I've surveyed specific kinds of aspects that can be effectively tried and used at different stages of maturity. There are as many useful aspects as there are useful objects, so I'd encourage you to think of ones that make sense for your system.

Throughout this tour, I've built on the four basic principles for success with aspects, which you saw at each stage of adoption:

  • Adopt incrementally: At each stage, apply aspects to what is achievable and valuable.
  • Reuse, then create: Seek to find existing solutions you can reuse or extend, rather than always inventing anew.
  • Invest in pleasant surprises: Ensure your colleagues have good first and subsequent experiences using aspects.
  • Learn with theory and practice (and continually sharpen your saw): Balance hands-on learning with reading some of the great references already available, including those highlighted in the Resources section below.

If you want to succeed with aspects, it's important to learn their value incrementally, positively demonstrate it to others in your organization, and then assist other developers as they start on the same process themselves. I hope this article has given you some good ideas for interesting uses of aspects at any stage of maturity, as well as guideposts for tackling the next stage. Many of the applications and principles discussed here have been covered in-depth in other articles in the AOP@Work series, so see Resources for further learning. I'd love to hear about your experiences!

Acknowledgments

Thanks to Nick Lesiecki, Ramnivas Laddad, Sergei Kojarski, Dean Wampler, Mik Kersten, Sian January, Brian Sletten, Matt Chapman, and Joe Shoop for reviewing this article carefully and providing me with the benefit of their wisdom. Thanks especially to Athen O'Shea for being such a great editor for this and my previous articles and to Jenni Aloi for supporting the series.

Resources

Learn

Get products and technologies

  • The AspectJ home page: Download the latest AspectJ compiler and tools.
  • The AJDT home page: Download the AspectJ Development Tools for Eclipse.
  • The Glassbox Inspector: A reusable library for performance monitoring using AspectJ and JMX.
  • The Spring framework: Contains a proxy-based AOP framework. Spring 2.0 contains a number of additional library aspects for AspectJ and Spring AOP.
  • The JBoss Cache: A distributed caching system, including an AOP API; one of several reusable aspects offered by JBoss.
  • Design Patterns in AOP: Contains reusable implementations of 12 of the Gang of Four patterns in AspectJ as well as in the Java language.
  • AspectJ Sandbox Project: Download a simple sandbox in Eclipse for experimenting with library aspects.

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. Select information in your profile (name, country/region, and company) is displayed to the public and will accompany any content you post. 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=106012
ArticleTitle=AOP@Work: Next steps with aspects
publish-date=03162006