I want my AOP!, Part 1

Separate software concerns with aspect-oriented programming

1 2 3 Page 2
Page 2 of 3

AOP, at its core, lets you implement individual concerns in a loosely coupled fashion, and combine these implementations to form the final system. Indeed, AOP creates systems using loosely coupled, modularized implementations of crosscutting concerns. OOP, in contrast, creates systems using loosely coupled, modularized implementations of common concerns. The modularization unit in AOP is called an aspect, just as a common concern's implementation in OOP is called a class.

AOP involves three distinct development steps:

  1. Aspectual decomposition: Decompose the requirements to identify crosscutting and common concerns. You separate module-level concerns from crosscutting system-level concerns. For example, in the aforementioned credit card module example, you would identify three concerns: core credit card processing, logging, and authentication.
  2. Concern implementation: Implement each concern separately. For the credit card processing example, you'd implement the core credit card processing unit, logging unit, and authentication unit.
  3. Aspectual recomposition: In this step, an aspect integrator specifies recomposition rules by creating modularization units -- aspects. The recomposition process, also known as weaving or integrating, uses this information to compose the final system. For the credit card processing example, you'd specify, in a language provided by the AOP implementation, that each operation's start and completion be logged. You would also specify that each operation must clear authentication before it proceeds with the business logic.
Figure 3. AOP development stages

AOP differs most from OOP in the way it addresses crosscutting concerns. With AOP, each concern's implementation remains unaware that other concerns are "aspecting" it. For example, the credit card processing module doesn't know that the other concerns are logging or authenticating its operations. That represents a powerful paradigm shift from OOP.

Note: An AOP implementation can employ another programming methodology as its base methodology, thus keeping the base system's benefits intact. For example, an AOP implementation could choose OOP as the base system to pass on benefits of better implementation of common concerns with OOP. With such an implementation, individual concerns could employ OOP techniques for each identified concern. That is analogous to a procedural language acting as the base language for many OOP languages.

Weaving example

The weaver, a processor, assembles an individual concern in a process known as weaving. The weaver, in other words, interlaces different execution-logic fragments according to some criteria supplied to it.

To illustrate code weaving, let's return to our credit card processing system example. For brevity, consider only two operations: credit and debit. Also assume that a suitable logger is available.

Consider the following credit card processing module:

public class CreditCardProcessor {
    public void debit(CreditCard card, Currency amount) 
       throws InvalidCardException, NotEnoughAmountException,
              CardExpiredException {
        // Debiting logic
    }
    
    public void credit(CreditCard card, Currency amount) 
        throws InvalidCardException {
        // Crediting logic
    }
}

Also, consider the following logging interface:

public interface Logger {
    public void log(String message);
}

The desired composition requires the following weaving rules, expressed here in natural language (a programming language version of these weaving rules is provided later in this article):

  1. Log each public operation's beginning
  2. Log each public operation's completion
  3. Log any exception thrown by each public operation

The weaver would then use these weaving rules and concern implementations to produce the equivalent of the following composed code:

public class CreditCardProcessorWithLogging {
    Logger _logger;
    public void debit(CreditCard card, Money amount) 
        throws InvalidCardException, NotEnoughAmountException,
               CardExpiredException {
        _logger.log("Starting CreditCardProcessor.credit(CreditCard,
Money) "
                    + "Card: " + card + " Amount: " + amount);
        // Debiting logic
        _logger.log("Completing CreditCardProcessor.credit(CreditCard,
Money) "
                    + "Card: " + card + " Amount: " + amount);
    }
    
    public void credit(CreditCard card, Money amount) 
        throws InvalidCardException {
        System.out.println("Debiting");
        _logger.log("Starting CreditCardProcessor.debit(CreditCard,
Money) "
                    + "Card: " + card + " Amount: " + amount);
        // Crediting logic
        _logger.log("Completing CreditCardProcessor.credit(CreditCard,
Money) "
                    + "Card: " + card + " Amount: " + amount);
    }
}

Anatomy of AOP languages

Just like any other programming methodology implementation, an AOP implementation consists of two parts: a language specification and an implementation. The language specification describes language constructs and syntax. The language implementation verifies the code's correctness according to the language specification and converts it into a form that the target machine can execute. In this section, I explain the parts and pieces of an aspect-oriented language.

The AOP language specification

At a higher level, an AOP language specifies two components:

  • Implementation of concerns: Mapping an individual requirement into code so that a compiler can translate it into executable code. Since implementation of concerns takes the form of specifying procedures, you can to use traditional languages like C, C++, or Java with AOP.
  • Weaving rules specification: How to compose independently implemented concerns to form the final system. For this purpose, an implementation needs to use or create a language for specifying rules for composing different implementation pieces to form the final system. The language for specifying weaving rules could be an extension of the implementation language, or something entirely different.

AOP language implementation

AOP language compilers perform two logical steps:

  1. Combine the individual concerns
  2. Convert the resulting information into executable code

An AOP implementation can implement the weaver in various ways, including source-to-source translation. Here, you preprocess source code for individual aspects to produce weaved source code. The AOP compiler then feeds this converted code to the base language compiler to produce final executable code. For instance, using this approach, a Java-based AOP implementation would convert individual aspects first into Java source code, then let the Java compiler convert it into byte code. The same approach can perform weaving at the byte code level; after all, byte code is still a kind of source code. Moreover, the underlying execution system -- a VM implementation, say -- could be aspect aware. Using this approach for Java-based AOP implementation, for example, the VM would load weaving rules first, then apply those rules to subsequently loaded classes. In other words, it could perform just-in-time aspect weaving.

AOP benefits

AOP helps overcome the aforementioned problems caused by code tangling and code scattering. Here are other specific benefits AOP offers:

  • Modularized implementation of crosscutting concerns: AOP addresses each concern separately with minimal coupling, resulting in modularized implementations even in the presence of crosscutting concerns. Such an implementation produces a system with less duplicated code. Since each concern's implementation is separate, it also helps reduce code clutter. Further, modularized implementation also results in a system that is easier to understand and maintain.
  • Easier-to-evolve systems: Since the aspected modules can be unaware of crosscutting concerns, it's easy to add newer functionality by creating new aspects. Further, when you add new modules to a system, the existing aspects crosscut them, helping create a coherent evolution.
  • Late binding of design decisions: Recall the architect's under/overdesign dilemma. With AOP, an architect can delay making design decisions for future requirements, since she can implement those as separate aspects.
  • More code reuse: Because AOP implements each aspect as a separate module, each individual module is more loosely coupled. For example, you can use a module interacting with a database in a separate logger aspect with a different logging requirement.

    In general, a loosely coupled implementation represents the key to higher code reuse. AOP enables more loosely coupled implementations than OOP.

AspectJ: An AOP implementation for Java

AspectJ, a freely available AOP implementation for Java from Xerox PARC, is a general-purpose aspect-oriented Java extension. AspectJ uses Java as the language for implementing individual concerns, and it specifies extensions to Java for weaving rules. These rules are specified in terms of pointcuts, join points, advice, and aspects. Join points define specific points in a program's execution, a pointcut is the language construct that specifies join points, advice defines pieces of an aspect implementation to be executed at pointcuts, and an aspect combines these primitives.

In addition, AspectJ also allows the "aspecting" of other aspects and classes in several ways. Indeed, you can introduce new data members and new methods, as well as declare a class to implement additional base classes and interfaces.

AspectJ's weaver -- an aspect compiler -- combines different aspects together. Because the final system created by the AspectJ compiler is pure Java byte code, it can run on any conforming JVM. AspectJ also features tools such as a debugger and selected IDE integration. I'll present AspectJ in more detail in Parts 2 and 3 of this series.

Below you'll find an AspectJ implementation of the logging aspect for the weaver I described in natural language above. Since I will present an AspectJ tutorial in Part 2, do not worry if you don't understand it in detail. The point that you should really notice is that the credit card processing itself does not know anything about logging:

public aspect LogCreditCardProcessorOperations {
    Logger logger = new StdoutLogger();
    pointcut publicOperation():
        execution(public * CreditCardProcessor.*(..));
    pointcut publicOperationCardAmountArgs(CreditCard card, 
                                           Money amount):
        publicOperation() && args(card, amount);
    before(CreditCard card, Money amount):
        publicOperationCardAmountArgs(card, amount) {
        logOperation("Starting",
             thisjoin point.getSignature().toString(), card, amount);
    }
    after(CreditCard card, Money amount) returning:
        publicOperationCardAmountArgs(card, amount) {
        logOperation("Completing",
            thisjoin point.getSignature().toString(), card, amount);
    }
    after (CreditCard card, Money amount) throwing (Exception e):
        publicOperationCardAmountArgs(card, amount) {
        logOperation("Exception " + e, 
            thisjoin point.getSignature().toString(), card, amount);
    }
    private void logOperation(String status, String operation, 
                              CreditCard card, Money amount) {
        logger.log(status + " " + operation + 
                   " Card: " + card + " Amount: " + amount);
    }
}

Do I need AOP?

Does AOP merely address design shortcomings? In AOP, the each concern's implementation is oblivious to the fact that it is being aspected by other concerns. This obliviousness sets AOP apart from OOP techniques. In AOP, the flow of composition runs from crosscutting concerns to the main concern, whereas in OOP techniques the flow runs in the opposite direction. Note, however, that OOP and AOP can merrily coexist. For example, you could employ a mix-in class as a composition using either AOP or OOP, depending upon your AOP comfort level. In either case, the mix-in class implementing a crosscutting concern doesn't need to know whether it is a mix-in class using hand composition or a part of an aspect using rule-based composition. For example, you could use a logger interface as a mix-in for some classes or as a logging aspect for others. Thus, there's an evolutionary path from OOP to AOP.

Get your AOP!

In this article you discovered the problems with crosscutting concerns, the current ways to implement them, and their shortcomings. You also examined how AOP helps overcome those problems. AOP methodology seeks to modularize crosscutting concern implementations, and it produces a better and faster software implementation.

If your software system comprises several crosscutting concerns, consider learning more about AOP, its implementations, and its benefits. AOP quite possibly represents the next big thing. Stay tuned for more in Parts 2 and 3 in future months.

Ramnivas Laddad is a Sun Certified Architect of Java Technology. He holds a master's degree in electrical engineering with a specialization in communication engineering. He has been architecting, designing, and developing software projects involving GUIs, networking, distributed systems, realtime systems, and modeling for over eight years. Ramnivas began working with object-oriented Java systems five years ago. Ramnivas, as a principal software engineer at Real-Time Innovations, Inc., leads the development of the next generation of ControlShell, a component-based programming framework for building complex realtime systems.
Related:
1 2 3 Page 2
Page 2 of 3