Java Development News:

Part 2 - The Techniques of the Trade

By Ramnivas Laddad

01 Dec 2003 | TheServerSide.com

Introduction

In the previous article of this series, we examined the fundamentals of aspect-oriented refactoring (AO refactoring), the general schemes and considerations involved, and the process through a simple example. By now, you should have a clear idea about AO refactoring. In this second and concluding part of the series, I will present several AO refactoring techniques.

We will examine AO techniques to refactor exception handling, concurrency control, argument trickle, worker object creation, interface implementation, overridden methods, lazy initialization, and contract enforcement. For the sake of brevity, we will focus on before-and-after code affected by refactoring and not so much on the steps leading to it. The process for each of the techniques is similar to the approach we presented in the first article. Further, I assume that all relevant conventional refactoring has been applied before we start AO refactoring. This avoids repeating information covered elsewhere (see “Resources”) and shows that AO refactoring indeed augments, and not replaces, conventional refactoring.

Many of the AO refactoring techniques presented in this article are useful in implementing crosscutting concerns at a wider scope. The difference, however, is in the emphasis placed on certain principles as outlined in the “Peculiarities of AO refactoring” section in part 1 of this series. In practice, it is very common to start by writing an aspect to refactor a class, then using the aspect to refactor multiple classes, and eventually modifying it to implement crosscutting in a system-wide fashion.

There are many techniques to cover. Let’s dive right in!

Extract exception handling

Exception handling is a crosscutting concern that affects most nontrivial classes. Due to the structure of exception handling code (try/catch blocks), conventional refactoring cannot perform further extraction of common code. Each class (or a set of classes) may have its own way to handle exceptions encountered during execution of its logic. With AO refactoring, you can extract exception handling code in a separate aspect.

We will illustrate the technique through an exception handling scheme, where handlers throw a new exception converting the caught exception to another type. It is possible to extend this example to other exception handling techniques such as logging and rethrowing, aborting transactions, and reattempting the operation.

Consider the Business Delegate J2EE pattern (see “Resources”). Almost every method in a business delegate class catches exceptions thrown by the underlying implementation and rethrows an application-specific exception. Implementing this exception handling scheme requires a try/catch block in each method. In each of the catch blocks, you create and throw a new exception that wraps the caught exception, after performing other tasks such as logging and rolling back the current transaction. The same situation occurs in many other design patterns such as Data Access Object and Service Locator.

Consider Listing 1 where class LibraryDelegate is using the Business Delegate pattern. There is duplicated logic present in almost all methods.

Listing 1: LibraryDelegate.java before refactoring
package library;
 
... imports
 
public
class LibraryDelegate {
    private LibrarySession _session;
 
    public LibraryDelegate() throws LibraryException {
        init();
    }
 
    private void init() throws LibraryException {
        try {
            LibrarySessionHome home
                = (LibrarySessionHome)ServiceLocator.getInstance().
                    getRemoteHome("Library", LibrarySessionHome.class);
            _session = home.create();
        } catch (ServiceLocatorException ex) {
            throw new LibraryException(ex);
        } catch (CreateException ex) {
            throw new LibraryException(ex);
        } catch (RemoteException ex) {
            throw new LibraryException(ex);
        }
    }
 
    ... session management: reconnection, get/set current library etc with
    ... identical exception handling code
 

    public LibraryTO getLibraryDetails() throws LibraryException {
        try {
            return _session.getLibraryDetails();
        } catch (RemoteException ex) {
            throw new LibraryException(ex);
        }
    }
 
    public void setLibraryDetails(LibraryTO to) throws LibraryException {
        try {
            _session.setLibraryDetails(to);
        } catch (RemoteException ex) {
            throw new LibraryException(ex);
        }
    }
 
    public void addBook(BookTO book) throws LibraryException {
        try {
            _session.addBook(book);
        } catch (RemoteException ex) {
            throw new LibraryException(ex);
        }
    }
 
    public void removeBook(BookTO book) throws LibraryException {
        try {
            _session. removeBook(book);
        } catch (RemoteException ex) {
            throw new LibraryException(ex);
        }
    }
 
    ... other methods for adding/removing patrons, checkin/out books,
    ... get all books etc. with identical exception handling code

}

There isn’t any conventional refactoring technique to extract repeated try/catch blocks and the code associated with each. To refactor out all the exception handling logic, we write the following aspect. (Due to a bug in AspectJ 1.1.1, the current implementation, we cannot make this aspect a nested aspect. Therefore, we do the next best thing – make it a peer aspect):


aspect LibaryExceptionHandling {
    declare soft : RemoteException
        : call(* *.*(..) throws RemoteException) && within(LibraryDelegate);
    declare soft : ServiceLocatorException 
        : call(* *.*(..) throws ServiceLocatorException) && within(LibraryDelegate);
    declare soft : CreateException 
        : call(* *.*(..) throws CreateException) && within(LibraryDelegate);
 
    after() throwing(SoftException ex) throws LibraryException
        : execution(* LibraryDelegate.*(..) throws LibraryException)
          && within(LibraryDelegate) {
        throw new LibraryException(ex.getWrappedThrowable());
    }
}

In the aspect, each declare soft statement in LibaryExceptionHandlingAspect causes any exception of the specified types (RemoteException, ServiceLocatorException, CreateException) thrown during a call matching the specified pointcut to be treated as a runtime exception. When such an exception is thrown, it is wrapped in a SoftException, which is a runtime exception. The after throwing advice catches any SoftException thrown and throws a new LibraryException wrapping the original exception obtained by calling getWrappedThrowable() on the caught exception. Now we can take out all the exception handling from the core implementation. Listing 2 shows the LibraryDelegate.java after applying the “Extract exception handling” refactoring. Note that while the pointcuts in the above aspects heavily use wildcards, you would arrive at such a definition by following the process described in the first article of the series.

Listing 2: LibraryDelegate.java after refactoring
package library;
 
... imports
 
public class LibraryDelegate {
    private LibrarySession _session;
 
    public LibraryDelegate() throws LibraryException {
        init();
    }
 
    private void init() throws LibraryException {
        LibrarySessionHome home
            = (LibrarySessionHome)ServiceLocator.getInstance().
                getRemoteHome("Library", LibrarySessionHome.class);
        _session = home.create();
    }
 
    ... session management: reconnection, get/set current library etc.
 
    public LibraryTO getLibraryDetails() throws LibraryException {
        return _session.getLibraryDetails();
    }
 
    public void setLibraryDetails(LibraryTO to) throws LibraryException {
        _session.setLibraryDetails(to);
    }
 
    public void addBook(BookTO book) throws LibraryException {
        _session.addBook(book);
    }
 
    public void removeBook(BookTO book) throws LibraryException {
        _session. removeBook(book);
    }
 
    ... other methods for adding/removing patrons, checkin/out books,
    ... get books etc.

}
 
aspect LibaryExceptionHandling {
    declare soft : RemoteException
        : call(* *.*(..) throws RemoteException) && within(LibraryDelegate);
    declare soft : ServiceLocatorException 
        : call(* *.*(..) throws ServiceLocatorException) && within(LibraryDelegate);
    declare soft : CreateException 
        : call(* *.*(..) throws CreateException) && within(LibraryDelegate);
 
    after() throwing(SoftException ex) throws LibraryException
        : execution(* LibraryDelegate.*(..) throws LibraryException)
          && within(LibraryDelegate) {
        throw new LibraryException(ex.getWrappedThrowable());
    }
}

This is clearly a substantial saving in code and the core code is cleaner. The aspect has localized exception handling, thus simplifying modification to exception handling policy. For example, it is easy to add logging by modifying the advice.

One refactoring technique often leads to another. The refactoring described next often starts as a combination of “extract method calls” and “extract exception handing”.

Extract concurrency control

Concurrency control is often a critically important, but equally hard to implement, crosscutting concern. Besides being tough to understand, a concurrency control implementation requires the code to be scattered over many methods. There are a few concurrency control patterns available, and although the concepts are reusable, their implementations aren’t. AOP offers reusable implementations of those patterns, thus taking some pain out of concurrency control implementation.

Here is a simple example in Listing 3 that uses the read-write lock pattern (adapted from “AspectJ in Action”). The concurrency pattern requires managing two kinds of lock: read lock and write lock. Upon entering each method that only reads data, the read lock is acquired and that lock is released before leaving the method. The write lock is acquired and released in a similar manner for each method that modifies data.

Listing 3: Account.java before refactoring
import EDU.oswego.cs.dl.util.concurrent.*;
 
public class Account {
 
    ...
 
    private ReadWriteLock _lock = new ReentrantWriterPreferenceReadWriteLock();
 
    ... constructors etc.
 
    public void credit(float amount) {
        try {
            _lock.writeLock().acquire();
 
            ... business logic for credit operation ...
 
        } catch (InterruptedException ex) {
            throw new InterruptedRuntimeException(ex);
        } finally {
            _lock.writeLock().release();
        }
    }
 
    public void debit(float amount) throws InsufficientBalanceException {
        try {
            _lock.writeLock().acquire();
 
            ... business logic for debit operation ...
 
        } catch (InterruptedException ex) {
            throw new InterruptedRuntimeException(ex);
        } finally {
            _lock.writeLock().release();
        }
    }
 
    public float getBalance() {
        try {
            _lock.readLock().acquire();
 
            ... business logic for getting the current balance ...
 
        } catch (InterruptedException ex) {
            throw new InterruptedRuntimeException(ex);
        } finally {
            _lock.readLock().release();
        }
    }
 
    public void setBalance(float balance) {
        try {
            _lock.writeLock().acquire();
 
            ... business logic for setting the current balance ...
 
        } catch (InterruptedException ex) {
            throw new InterruptedRuntimeException(ex);
        } finally {
            _lock.writeLock().release();
        }
    }
 
    ... more methods
 
    public String toString() {
        try {
            _lock.readLock().acquire();
 
            ... form the description string  ...
 
        } catch (InterruptedException ex) {
            throw new InterruptedRuntimeException(ex);
        } finally {
            _lock.readLock().release();
        }
    }
}

The solution uses a reusable ReadWriteLockSynchronizationAspect aspect (see source). The aspect declares two abstract pointcuts: readOperations()and writeOperations(), and advises them to manage read and write lock. The aspect uses a perthis association to maximize code reuse. However, the details of the perthis aspect association are beyond the scope of this article. Note that the reusable base aspect may be a result of following the “Refactor the refactoring aspect” step as outlined in the part 1 of this series.

We refactor the concurrency control code for the Account class in a nested aspect. This aspect is a concrete subaspect of ReadWriteLockSynchronizationAspect. We provide the definition for abstract pointcuts: readOperations()and writeOperations(). In the Account class’ case (after going through the process described in part 1 of this series), we define read operations as all methods with their name starting in “get” as well as the toString() method. We consider every other method as a write operation.

static aspect ConcurrencyControlAspect
    extends ReadWriteLockSynchronizationAspect {
    public pointcut readOperations()
        : (execution(* Account.get*(..)) || execution(* Account.toString(..)))
          && within(Account);
 
    public pointcut writeOperations()
        : (execution(* Account.*(..)) && !readOperations())
          && within(Account);
    }
}

 
Listing 4 shows the Account.java file after applying refactoring to encapsulate concurrency control in the nested aspect. The Account class looks like the following:
 
Listing 4: Account class after refactoring
public abstract class Account {
 
    ...
 
    ... no _lock variable here (compared to Listing 3)
 
    ... constructors etc.
 
 
    public void credit(float amount) {
        ... business logic for credit operation ...
    }
 
    public void debit(float amount) throws InsufficientBalanceException {
        ... business logic for debit operation ...
    }
 
    public float getBalance() {
        ... business logic for setting the current balance ...
    }
 
    public void setBalance(float balance) {
        ... business logic for getting the current balance ...
    }
 
    ... more methods   
 
    static aspect ConcurrencyControlAspect
        extends ReadWriteLockSynchronizationAspect {
        public pointcut readOperations()
            : (execution(* Account.get*(..)) || execution(* Account.toString(..)))
              && within(Account);
 
        public pointcut writeOperations()
            : (execution(* Account.*(..)) && !readOperations())
              && within(Account);
    }
}

The refactoring saves a good amount of code, ensures consistently acquiring and releasing the right locks, and isolates details of the read-write lock pattern. As a bonus, you can easily change implementations. For example you can use a simpler scheme that surrounds each read and write operation in a synchronized block. All you need to do is change the base aspect. The change is highlighted in the code.

public abstract class Account {
 
    ... core implementation unchanged from Listing 4 ...
 
    static aspect ConcurrencyControlAspect
        extends SimpleSynchronizationAspect {
        public pointcut readOperations()
            : (execution(* Account.get*(..)) || execution(* Account.toString(..)))
              && within(Account);
 
        public pointcut writeOperations()
            : (execution(* Account.*(..)) && !readOperations())
              && within(Account);
    }
}

The reusable base aspect SimpleSynchronizationAspect (see source) simply surrounds each join point captured by either of the pointcuts in a synchronized block. The use of AOP created separation of concerns between the Account implementation and concurrency control implementation, which allowed such an easy modification.

The techniques described so far use the simple crosscutting constructs. The use of AOP design patterns help in creating powerful refactoring techniques. The two techniques make the use of AOP design patterns to extract common code that crosscuts many methods.

Extract worker object creation

A worker object is an instance of a class that encapsulates a method (called a worker method) so that the method can be treated like an object. You need to create worker objects in many situations: executing methods asynchronously, performing authorization using Java Authentication and Authorization Service (JAAS) API, implementing thread safety in Swing/SWT applications, and so on. On each occasion, a lot of extra code is required to create such worker objects. For each worker method, you need to create a named or anonymous class that encapsulates the required method call and create an instance of such a class. If you use named classes, you end up with many classes that just execute a worker method and if you use anonymous class, the code to create such a class overwhelms the core logic. The result is hard to understand and maintain code.

We can devise an AO refactoring that uses the worker object creation pattern to help keep the core implementation simple and to localize the worker creation and usage logic. While the details of the worker object creation pattern are beyond the scope of this article, here is a quick summary of the pattern: AspectJ’s around advice allows proceed() (which executes the captured join point) to be called from anywhere in the advice body. The pattern utilizes this fact and calls proceed() from an anonymous class in the advice. See “Resources” for more information about this pattern.

Listing 5 shows the ATM class before refactoring. The use of the JAAS authorization scheme requires executing the method by passing a worker object to Subject.doAsPrivileged(). Therefore, we use an anonymous class that executes the business logic in its run() method. We pass an instance of the anonymous class to Subject.doAsPrivileged() (in the overall implementation, you must call checkPermission() in the methods that need an authorization check). See the example in the first part of the article.

Listing 5: ATM.java before refactoring
... imports
 
public class ATM {
    private Subject _atmSubject;
   
    ...    
 
    public float getBalance(final Account account)
        throws BankingException {
        PrivilegedAction worker
            = new PrivilegedAction() {
                public Object run() {
                    BankLiaison bl = ...
                    return new Float(bl.getBalance(account));
               }};  
    
        Float balance
            = (Float)Subject.doAsPrivileged(_atmSubject, worker, null);
        return balance.floatValue();
    }
 
 
    public void credit(final Account account, final float amount)
        throws BankingException {
        PrivilegedExceptionAction worker
            = new PrivilegedExceptionAction() {
                public Object run() throws Exception {
                    BankLiaison bl = ...
                    bl.credit(account, amount);
                    return null;
              }};
 
        try {
            Subject.doAsPrivileged(_atmSubject, worker, null);
        } catch (PrivilegedActionException ex) {
            Throwable cause = ex.getCause();
            throw new BankingException(ex.getCause());
        }
    }
 
    ... identical JAAS authorization for other methods accessing BankingLiaison
}

Clearly, the code is hard to understand and takes a while to figure out the business logic buried inside the overwhelming amount of code for the anonymous classes. We can refactor the authorization logic by using the worker object creation pattern as shown in the following aspect.

private static aspect AuthorizationRouterAspect {
     pointcut authOperations(ATM atm)
         : execution(public * ATM.*(..)) && this(atm) && within(ATM);
 
     Object around(final ATM atm) throws BankingException
        : authOperations(atm) {
        PrivilegedExceptionAction worker
            = new PrivilegedExceptionAction() {
                   public Object run() throws Exception {
                       return proceed(atm);
                   }
              };
 
        try {
            return Subject.doAsPrivileged(atm._atmSubject, worker, null);
        } catch (PrivilegedActionException ex) {
            return new BankingException(ex);
        }
    }
}

Listing 6 shows the code for the ATM class after applying the refactoring.

Listing 6: ATM.java after refactoring
... imports
 
public class ATM {
    private Subject _atmSubject;
   
    ...   
 
    public float getBalance(Account account) throws BankingException {
        BankLiaison bl = ...
        return bl.getBalance(account);
    }
 
    public void credit(Account account, float amount) throws BankingException {
        BankLiaison bl = ...
        bl.credit(account, amount);
    }
 
    ...
 
    private static aspect AuthorizationRouterAspect {
         pointcut authOperations(ATM atm)
             : execution(public * ATM.*(..)) && this(atm) && within(ATM);
 
        Object around(final ATM atm) throws BankingException
            : authOperations(atm) {
            PrivilegedExceptionAction action
                = new PrivilegedExceptionAction() {
                       public Object run() throws Exception {
                           return proceed(atm);
                       }};
 
            try {
                return Subject.doAsPrivileged(atm._atmSubject, action, null);
            } catch (PrivilegedActionException ex) {
                return new BankingException(ex);
            }
        }
    }
}

Note that if you route methods that throw a business-specific exception, you will need additional logic in the aspect to deal with it. For example, from the preceding aspect, when applied to the debit() method that throws InsufficientBalanceException, the client will receive a generic BankingException. However, we will not consider that issue in this article; for more information, review the exception introduction pattern (as described in “AspectJ in Action” listed under “Resources”).

So far, we have focused on refactoring common functionality in one class at a time. The next two refactoring techniques consider a set of related classes together as the refactoring target.

Replace argument trickle by wormhole

Often, there is a need to pass a part of the current context (current execution/target object, method arguments etc.) to invoked methods, which in turn pass it along, so that a called method down the call chain eventually may use the context to perform its task. The result is API pollution due to a crosscutting concern and increased cognitive burden for the developer of the classes in the middle of the chain. This also causes significant problems for reusing components. With AO refactoring, you can avoid passing the parameters to methods in the call chain. This is one of the more invasive AO refactoring techniques as it typically cuts across two or more classes.

Consider fragments of a few classes (Listing 7) in a banking system. The ATM class utilizes a BankingLiaison instance (that represents the bank corresponding to the ATM card) which, in turn, invokes operation on the Account instance. Account statement generation needs information about the accessed ATM in addition to the account, the amount, and the operation involved. To facilitate statement generation, ATM’s methods pass the accessed ATM to the methods of BankLiaison, which propagate it to the Account class’ methods. The Account class’ methods invoke appendAccountActivities() which appends activities, most likely, to a database table.

Listing 7: Banking classes before refactoring
public class ATM {
 
    ...
 
    public void credit(Account account, float amount) {
        BankLiaison bl = ... // agent for the bank based on the card info
        bl.credit(this, account, amount);
    }
 
    public void debit(Account account, float amount)
        throws InsufficientBalanceException {
        BankLiaison bl = ... // agent for the bank based on the card info
        bl.debit(this, account, amount);
    }
 
    ... more operations
}
 
public class BankLiaison {
 
    ...
 
    public void credit(ATM atm, Account account, float amount) {
        account.credit(atm, amount);
    }
 
    public void debit(ATM atm, Account account, float amount)
        throws InsufficientBalanceException {
        account.debit(atm, amount);
    }
 
    ... more operations
 
}
 
public class Account {
 
    ...
 
    public void credit(ATM atm, float amount) {
 
        ... credit business logic
 
        appendAccountActivities(atm, this, amount, "credit");       
    }
 
    public void debit(ATM atm, float amount) {
 
        ... debit business logic
 
        appendAccountActivities(atm, this, amount, "debit");       
    }
 
    ... more operations
 
    private static void
        appendAccountActivities(ATM atm, Account account,
                                float amount, String operation) {
        ... update database
    }
}

A problem with the above code is that the ATM parameter is trickled through layers of calls (highlighted in the code). Note that the example uses only three class levels and one parameter. The problem becomes more acute as the number of level and call parameters increase.

We will devise an AO refactoring technique based on the Wormhole pattern to help in this situation. While the details of the pattern are beyond the scope of this article, essentially it uses two pointcuts – one for the caller that has the context and one for the callee that needs the context. Then the pattern creates a wormhole using a cflow() pointcut and transfers the caller context to the call join point site. See “Resources” for more information about the wormhole pattern. Note that there is an alternative using a ThreadLocal variable, to hold the ATM variable, but the solution that uses the pattern is cleaner because it modularizes access to the information, rather than using a global variable. As an aside, even the scheme using a ThreadLocal variable is more effective when used with aspects because it is modularized!

We refactor the logic of updating the account activities table in a nested aspect of the Account class. While we show directly the final result of the refactoring effort, applying the “Extract method calls” prior to utilizing the wormhole pattern may be a good idea. Here is the aspect that performs the refactoring:

static aspect UpdateAccountActivitiesTable {
    pointcut atmRequest(ATM atm)
        : execution(* ATM.*(..)) && this(atm) && within(ATM);
          
    pointcut accountOperation(Account account, float amount)
        : (execution(void Account.credit(float))
           || execution(void Account.debit(float)))
          && this(account) && args(amount) && within(Account);
          
    // wormhole
    pointcut accountOperationsInAtm(ATM atm, Account account, float amount)
        : accountOperation(account, amount) && cflow(atmRequest(atm));
          
    after(ATM atm, Account account, float amount) returning
        : accountOperationsInAtm(atm, account, amount) {
        appendAccountActivities(atm, account, amount,
                                thisJoinPointStaticPart.getSignature().getName());
    }
 
    private static void
        appendAccountActivities(ATM atm, Account account,
                                float amount, String operation) {
        ... update database
    }
}

The reason we have chosen to implement the aspect as a nested aspect of the Account class, as opposed to, say, the ATM class, is to avoid an additional dependency compared to the conventional implementation. A separate top-level aspect would be a good choice, too. Note that we have moved the appendAccountActivities() method from Account to the aspect, since only the aspect uses it. Listing 8 shows the banking classes after refactoring.

Listing 8: Banking classes after refactoring

public class ATM {

 

    ...

 

    public void credit(Account account, float amount) {

        BankLiaison bl = ... // agent for the bank based on the card info

        bl.credit(account, amount);

    }

 

    public void debit(Account account, float amount)

        throws InsufficientBalanceException {

        BankLiaison bl = ... // agent for the bank based on the card info

        bl.debit(account, amount);

    }

 

    ... more operations: debit, getBalance, transfer

}

 

public class BankLiaison {

 

    ...

 

    public void credit(Account account, float amount) {

        account.credit(amount);

    }

 

    public void debit(Account account, float amount)

        throws InsufficientBalanceException {

        account.debit(amount);

    }

 

    ...

 

}

 

public class Account {

 

    ...

 

    public void credit(float amount) {

 

        ... credit business logic

 

    }

 

    public void debit(float amount) throws InsufficientBalanceException {

 

        ... debit business logic

 

    }

 

    ...

 

    static aspect UpdateAccountActivitiesTable {

        pointcut atmRequest(ATM atm)

             : execution(* ATM.*(..)) && this(atm) && within(ATM);

          

        pointcut accountOperation(Account account, float amount)

            : (execution(void Account.credit(float))

               || execution(void Account.debit(float)))

              && this(account) && args(amount) && within(Account);

 

        // wormhole

        pointcut accountOperationsInAtm(ATM atm, Account account, float amount)

            : accountOperation(account, amount) && cflow(atmRequest(atm));

          

        after(ATM atm, Account account, float amount) returning

            : accountOperationsInAtm(atm, account, amount) {

            appendAccountActivities(atm, account, amount,

                                    thisJoinPointStaticPart.getSignature().getName());

        }

 

        private static void

            appendAccountActivities(ATM atm, Account account,

                                    float amount, String operation) {

            ... update database

        }

    }

}

 

The ATM, BankingLiaison, and Account classes no longer have an additional ATM parameter that is used for the sole purpose of updating the account activity database tables.

So far, we have mostly relied on dynamic crosscutting techniques for AO refactoring. The next technique illustrates the use of static crosscutting offered by AspectJ to refactor the existing code.

Extract interface implementation

The conventional refactoring technique of “Extract interface” allows improved decoupling of clients from implementations. If more than one class implements that interface, you may end up duplicating code required to implement the interface. With AOP, you can take the idea further and avoid any duplication.

AO refactoring uses AspectJ’s inter-type declaration mechanism (also known as introduction). You can write an aspect that introduces the default implementation into the extracted interface. In essence, AspectJ allows implementing mixins. This kind of refactoring is especially useful when the implementation of an interface is mostly boilerplate.

Let’s consider the ServiceCenter interface in Listing 9:

Listing 9: ServiceCenter.java before refactoring
public interface ServiceCenter {
    public String getId();
    public void setId(String id);
      
    public String getAddress();
    public void setAddress(String address);
}

The ATM class implements the ServiceCenter interface. Listing 10 shows the ATM class before any AO refactoring:

Listing 10: ATM.java before refactoring
public class ATM extends Teller implements ServiceCenter {
    private String _id;
    private String _address;
 
    ...
       
    public String getId() {
        return _id;
    }
       
    public void setId(String id) {
        _id = id;
    }
   
    public String getAddress() {
        return _address;
    }
       
    public void setAddress(String address) {
        _address = address;
    }
 
    ...
 
}

The ATM class contains a very boilerplate implementation of ServiceCenter. Other classes such as BrickAndMortarBank, SuperStoreServiceCenter will be similar – each one repeating the code to implement the ServiceCenter interface. While you could avoid duplicated code by creating the default implementation of the interface and make the classes extend the default implementation, the technique fails for the classes that are already extending another class.

With AO refactoring, we introduce the default implementation into the ServiceCenter interface using a nested aspect as shown in Listing 11:

Listing 11: ServiceCenter.java after refactoring
public interface ServiceCenter {
    public String getId();
    public void setId(String id);
   
    public String getAddress();
    public void setAddress(String address);
   
    static abstract aspect IMPL {
        private String ServiceCenter._id;
        private String ServiceCenter._address;
       
        public String ServiceCenter.getId() {
            return _id;
        }
       
        public void ServiceCenter.setId(String id) {
            _id = id;
        }
   
        public String ServiceCenter.getAddress() {
            return _address;
        }
       
        public void ServiceCenter.setAddress(String address) {
            _address = address;
        }
    }
}

In listing 11, the nested IMPL aspect introduces data members as well as methods with the default implementation of the ServiceCenter interface. With this aspect present, any class that implements the interface automatically inherits the default implementation.

Now we can take out the implementation of the methods declared in ServiceCenter from the ATM class as shown in Listing 12.

Listing 12: ATM.java after refactoring
public class ATM extends Teller implements ServiceCenter {
 
    ...
 
}

The other classes implementing the interface such as BrickAndMortarBank, SuperStoreBranch can remove the implementation of the methods declared in the interface, too. The implementing classes no longer include boilerplate code to implement the interfaces. The implementing classes, however, can still override the default implementation provided by the aspects.

There are a few variations of this refactoring technique. First, you may let the implementing classes choose whether to inherit the default implementation provided by the aspect. One way to implement this scheme is to create a subinterface of the original interface, say, ServiceCenterDefaultAspectImpl, and let the aspect introduce the default implementation to this interface. The implementing classes will inherit the default implementation only if they declare themselves to implement ServiceCenterDefaultAspectImpl. Another variation is to provide only the default implementation for a part of the interface. While in some cases, such a choice may be a sheer necessity (due to lack of information needed for implementation), in other cases, this may be a deliberate design decision to force developers of the classes to think about the right implementation semantics.

The rest in short

In this article, we have examined a few AO refactoring techniques. We will conclude with a brief discussion of a few other techniques.

Replace override with advice

It is often required to augment additional common behavior to many methods of a class. A typical solution is to create a subclass and override methods to perform some additional logic. For example, you may have a model class without any support for observer notification. You can add such a support by creating a subclass and overriding each state-modifying method to notify the observers.

With AO refactoring, you can use an aspect to advise the needed method with additional logic. For example, instead of overriding, you may simply advise methods of the subclass that notifies the observers. The implementation is much like the “Extract method calls” technique presented in the first part of the article. The difference is that once you extract method calls, the methods in the subclass simply call the corresponding base class method, and therefore need not exist in the derived class.

Extract Lazy initialization

Lazy initialization of expensive resources is a common optimization technique. The conventional solution requires checks for uninitialized resources in every place that uses those resources and causes code-scattering and code-tangling. It may seem that you can solve the code-scattering and code-tangling problem by simply accessing the instance variable through its getter method; however, there is a caveat: if a portion of the class holding the resource reference accesses it directly, initialization will not occur and you will experience undesired consequences. Testing could reveal such a bug, but only if the errant method is called before another method that leads to resource initialization. Note that setting private access to a class member will prevent direct access from other classes, but not from within the class itself. With AO refactoring, you can advise read access to the resource (using a get() pointcut) to initialize it.

Extract contract enforcement

Contract enforcements often require duplicated code in many methods of a class. This is especially true for implementing class invariants and pre- and post-conditions that are common to many methods. Conventional implementations require adding identical code – conditional checks and assert statements – into many methods. With AO refactoring, you can refactor such contract checks into a separate aspect. This kind of refactoring is much like “Extract method calls” that we discussed in the first part of this series, except you typically use assert statements to perform the additional logic.

Conclusion

Aspects that come from refactoring typically build up from affecting a small portion of a system, often starting with just a single class. While limiting the scope reduces benefits of those aspects, it also reduces risk of unwanted changes in system behavior. Over time, refactoring techniques can build up more broadly scoped aspects. In this article, we examined several techniques with prototypical examples that a Java developer faces. Initially, you may use the technique in the scenarios described. Overtime, you will develop an eye for applying these techniques to different scenarios and you may even uncover newer techniques.

Refactoring techniques are useful to understand by themselves. However, it will be much better when IDEs help AO refactoring as they do conventional refactoring. A simple support for AO refactoring would let programmers choose multiple blocks of code and the kind of refactoring desired. The IDE could then create a simple version of an aspect encapsulating the common elements from the selected code. If appropriate, the programmers could improve the pointcut definitions in the aspect created by the IDE. An advanced feature might provide hints on capturing commonalities between the pointcuts. IDEs could also support aspect mining, using hints supplied by programmer, to find refactoring opportunities (see “Resources” for existing tools offering such functionality).

Aspect-oriented programming significantly benefits real-world programming. However, understandably, there is a cautionary approach towards adopting it. A safe adaptation path for any technology is the one that allows for gradual use. For AOP, such a path seems to be using development aspects initially, followed by refactoring using AO techniques, then implementing complex crosscutting concerns, and finally designing systems with AOP right from the project’s inception. Such a path minimizes the associated risk, improves the understanding of AOP fundamentals, creates awareness of issues surrounding it, gives a realization of its appropriate and inappropriate usages, uncovers recurring design patterns, and all the while, boosts confidence in AOP.

The benefits of AOP are too real to ignore, and the cautionary approach towards it is too valid to dismiss. Utilizing a safe path of incorporating development and AO refactoring aspects help in overcoming this dilemma. First, make yourself comfortable with AOP using development aspects. Then when you see duplicated code that cannot be refactored using conventional techniques (you won’t have to look that hard!), take that opportunity to use AO refactoring. You will have gained immediate benefits and taken a step towards tapping full power of AOP.

Acknowledgements

Thanks to Ron Bodkin, Mik Kersten, Gregor Kiczales for reviewing the series and providing useful feedback.

Author’s bio

Ramnivas Laddad is the author of several articles, papers, and books. His most recent book, "AspectJ in Action: Practical aspect-oriented programming" (Manning, 2003), has been labeled as the most useful guide to AOP/AspectJ. Ramnivas has been developing complex software systems using technologies such as Java, J2EE, AspectJ, UML, networking, real-time systems, and XML for over a decade. He is an active member of the AspectJ user community and has been involved with aspect-oriented programming from its early form. He is also a mentor at AspectMentor, a consortium of AOP experts who provide assistance with training, consulting, and mentoring. You can reach him at ramnivas@yahoo.com

References

  • Understand conventional refactoring techniques:
    Martin Fowler, Kent Beck, John Brant, William Opdyke, Don Roberts, Refactoring: Improving the Design of Existing Code, Addison Wesley, 1999.

  • Know all about AOP and AspectJ, with many examples ranging from simple logging and policy enforcement to authorization and transaction management. You can also get detailed information about the AOP patterns used in this article such as the wormhole pattern, the worker object creation pattern, and the exception introduction pattern. The examples presented in this book should provide ideas for AO refactoring:
    Ramnivas Laddad, AspectJ in Action: Practical Aspect-Oriented Programming, Manning, 2003.

    TheServerSide.com features two sample chapters from “AspectJ in Action”:
    /articles/AspectJreview.tss

  • Find more about the fundamental of AOP and AspectJ (based on AspectJ 1.0, but most information is still applicable):
    Ramnivas Laddad, I want my AOP (part 1-3), JavaWorld, January 2002.

  • Understand whys and hows behind AOP:
    Gregor Kiczales, Crosscut (monthly column) Software Development magazine.

  • Read more about the AspectJ programming language:
    Joseph D. Gradecki, Nicholas Lesiecki, Mastering AspectJ: Aspect-Oriented Programming in Java, John Wiley & Sons, 2003.

  • Find more information on J2EE design patterns discussed in this article:
    Dan Malks, Deepak Alur and John Crupi, Core J2EE Patterns: Best Practices and Design Strategies, 2nd edition. Prentice Hall, 2003.

  • Read another good source for enterprise design patterns:
    Martin Fowler, Patterns of Enterprise Application Architecture, Addison-Wesley, 2002.

  • Read more about Doug Lea’s concurrency library. The read-write lock pattern used on “Extract concurrency refactoring” uses this library:
    http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html

  • Download AspectJ tools, documentation, and links to plugins for various IDEs:
    http://www.eclipse.org/aspectj

  • Download tool that helps to find and manage crosscutting concerns, Aspect Browser:
    http://www.cs.ucsd.edu/users/wgg/Software/AB

  • Download Eclipse plugin that helps to explore crosscutting concerns in an existing system, Feature Exploration and Analysis Tool (FEAT):
    http://www.cs.ubc.ca/labs/spl/projects/feat

  • Download tool to mine aspects in an existing system, Aspect Mining Tool (AMT):
    http://www.cs.ubc.ca/~jan/amt

  • Download AspectJRB, an aspect-aware refactoring tool:
    http://dawis.informatik.uni-essen.de/site/research/aop/aspectjrb/index.html

  • A tool in making that will support Dialogue-Based Aspect-Oriented Refactoring:
    http://www.cs.ubc.ca/labs/spl/projects/ao-refactoring.html