ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Lazy Loading with Aspects

by Russell Miles
03/17/2004

In this article you will discover how Aspect Oriented Programming with AspectJ can apply Lazy Loading to areas within an application. By using the simple example provided here to control when an object is loaded, you can apply lazy loading to your own applications wherever you feel it is required.

So What Is Lazy Loading?

Lazy loading is a characteristic of an application when the actual loading and instantiation of a class is delayed until the point just before the instance is actually used. The goal is to only dedicate memory resources when necessary by only loading and instantiating an object at the point when it is absolutely needed.

Tools such as Eclipse have popularized the lazy-loading approach as they use the facility to control the load and initialization of heavyweight plug-ins. This gives the double bonus of speeding up the initial load time for the application, as not all plug-ins are loaded straightaway; ensuring efficiency as only the plug-ins that are used are loaded at all. This article will demonstrate how lazy loading can be achieved in your application where certain features of the design do not need to be loaded up front but rather on demand.

Lazy Loading with Traditional Object Orientation

Traditional Object Oriented (OO) methods for providing lazy loading usually lead to business logic being interleaved with the lazy loading code. Figure 1 shows a traditional object interaction sequence where lazy loading is applied.

Figure 1
Figure 1. Lazy loading using traditional Object Oriented techniques.

The main class in this simple example invokes the proxy objects directly. The proxy objects (of class LazyFeature) each encapsulate how the lazy loading takes place and load and initialize the implementation objects (of class FeatureA) that they are providing a proxy for.

The main problem with this approach is that the lazy-loading architecture is interleaved with the business logic encapsulated within the individual implementation classes and the main class. Although the architecture is simple, the lazy-loading capability is closely coupled to the classes involved and it could prove difficult to remove the lazy loading or even to apply new lazy-loading techniques, at a later point in time.

Lazy Loading with Aspects

Lazy loading is really an overall policy that should be adopted for the loading and initialization of declared types of objects, and therefore it is not directly related to the business logic of the application. For this reason lazy loading is actually a crosscutting concern. Aspect Orientation (AO) is an ideal candidate for improving the modularity of the implementation by de-coupling the crosscutting concern, in this case lazy loading, from the business logic. Figure 2 shows how an AO approach could modularize lazy loading in the same example application.

Figure 2
Figure 2. Lazy loading using Aspect Oriented techniques.

In the AO example the main program is not closely coupled to the lazy-loading classes. In fact, it has no knowledge of the lazy loading taking place beyond the initializeFeature(String) call that is used to create a new Feature object. The aspect itself controls all lazy-loading policies by capturing join points on calls to the Feature objects and delegating those calls where necessary to newly created or existing implementation class instances.

All lazy loading is controlled by the Aspect, modularizing the concern and providing a single place for lazy-loading policy enforcement. This is achieved without the main program, the proxy, or individual implementation classes having any knowledge of the lazy-loading policy.

A Practical Example

The following example gives a flavor of how lazy loading could be applied using Java and AspectJ. The full code for this example is available as a compressed .zip file and it is highly recommend that you download and compile the example in Eclipse with the AspectJ Development Tool plug-in installed. As well, any AspectJ development environment should work with some tweaking.

First a simple application is declared as shown in the following code sample. The MainApplication class simply loads the features according to their configurations (in this case String representations of the class names) and then sends example calls to the various features that it holds. The lazy-loading policy is simply that the classes that implement the Feature interface will not be loaded and initialized until the first calls to the methods on the feature, but this is not of concern to the main class.

public class MainApplication
{
   private Feature[] features;

   /**
    * Overriding the default constructor to 
    * show an example usage of the Lazy Loading 
    * capability.
    */
   public MainApplication()
   {
      features = new Feature[2];

      features[0] =
         LazyLoading
            .aspectOf()
            .initializeFeature(
            "features.FeatureA");

      features[1] =
         LazyLoading
            .aspectOf()
            .initializeFeature(
            "features.FeatureB");

      features[0].doSomething("Hello there");
      features[0].doSomething("Hello again");

      features[1].doSomething("Hi to you too");
      features[1].doSomething("Hi again");
   }

   /**
    * The main program that runs this example.
    * @param args The arguments passed from the 
    *             command line
    */
   public static void main(String[] args)
   {
      MainApplication mainApplication =
         new MainApplication();
   }
}

The rest of the example architecture includes the Feature interface shown in the next code snippet. The Feature interface declares the methods that any feature, lazy loaded or not, must fulfill. Also an example FeatureA class is provided that implements the Feature interface and could be provided by a third party. Once again, neither the interface nor the specialized implementation classes need to have any knowledge of the proposed lazy-loading policy.

public interface Feature
{
   /**
    * A simple example method on the feature.
    * @param message Some data being sent to 
    *                the feature.
    */
   public void doSomething(String message);
}

public class FeatureA implements Feature
{
   public void doSomething(String message)
   {
      System.out.println(
         "doSomething called in "
            + this
            + ", with: "
            + message);
   }
}

A proxy, the LazyFeatureProxy class, is declared to encapsulate the creation calls to the distinct feature implementation classes as shown in the next code snippet. This simple proxy also adds the behavior necessary for the lazy-loading policy to complete its function.

public class LazyFeatureProxy implements Feature
{
   private String featureClass;
   private Feature delegate;

   /**
    * The constructor for the LazyFeatureProxy 
    * class. This example simply sets up the 
    * proxy with the class that is to be used 
    * when instantiating objects as needed. 
    * This could also be configured in a more 
    * application specific way, such as using XML 
    * configuration information for example.
    * @param featureClass The string class name 
    *                     of the class being 
    *                     proxied.
    */
   public LazyFeatureProxy(String featureClass)
   {
      this.featureClass = featureClass;
   }

   /**
    * This method initializes the feature 
    * when the implementation is actually 
    * needed.
    * @return Feature the initialized concrete 
    *         feature.
    * @throws IllegalAccessException
    * @throws InstantiationException
    */
   public Feature getFeature()
      throws
         IllegalAccessException,
         InstantiationException,
         ClassNotFoundException
   {
      if (this.delegate == null)
      {
         return this.delegate =
            (Feature) Class
               .forName(this.featureClass)
               .newInstance();
      }
      else
      {
         return this.delegate;
      }
   }
}

Introducing the Aspects

Finally, four aspects are declared to encapsulate lazy loading in the example application. The ProxyPattern, DelegatingProxyPattern, LazyLoading, and TraceLazyInitialization aspects.

The ProxyPattern aspect and its derived DelegatingProxyPattern aspect are not shown here, but can be found in the full source for this example. Both of these aspects provide a hierarchy that supports a generic reusable abstract foundation that encapsulates the GoF Proxy Design Pattern, in particular the delegation characteristics of the pattern.

The Proxy Pattern, particularly in its delegation mode as the DelegatingProxyPattern that specializes the pattern to, is an ideal base for a lazy-loading system, as it provides a means by which control can be gained to any and all calls to an object. The original Proxy Pattern AspectJ implementation adapted for this example came from the research completed by Gregor Kiczales and Jan Hannemann.

The LazyLoading aspect as shown in the following code snippet encapsulates the specific application of the proxy pattern to the lazy-loading policies. It defines the logic to capture messages for the concrete target objects so that they can be initialized if necessary and then the message passed on. If the object already exists then the message is simply passed on without any additional instantiation.

public aspect LazyLoading 
   extends DelegatingProxyPattern
{
	
   /** 
    * Assigns the Subject role to Feature.
    */
   declare parents : Feature implements Subject;
   
   /** 
    * Provides a default implementation of the 
    * doSomething(String) method to 
    * stop implementations within this application 
    * from having to implement this method. This 
    * will deliberately not include features 
    * developed by third parties that must still 
    * implement the Feature interface fully).
    */
   public void Feature.doSomething(
      String message)
   {
   }
   
   /** 
    * Provides a means by which Features can 
    * be made available.
    */
   public Feature initializeFeature(
                      String featureClass)
   {
      Feature feature =
         new LazyFeatureProxy(featureClass);
      return feature;
   }
   
   /**
    * Captures all accesses to the subject that 
    * should be protected by this pattern 
    * instance. In this case all calls to 
    * RealSubject.write(..).
    */
   protected pointcut requestTriggered() : 
      call(* features.Feature.* (..)) &&
      !within(feature_management.LazyLoading +);
   
   /**
    * Delegates the call to a newly initialised 
    * object if not previously initialised.
    */
   protected Object delegateRequest(
      Object caller,
      Subject subject,
      JoinPoint joinPoint)
   {
      if (subject instanceof LazyFeatureProxy)
      {
         LazyFeatureProxy feature =
            (LazyFeatureProxy) subject;
         try
         {
            Feature implementedFeature =
               feature.getFeature();

			implementedFeature.doSomething(
               (String) joinPoint.getArgs()[0]);
         }
         catch (Exception e)
         {
            System.out.println(
               "Exception when attempting to "
                  + "lazy load"
                  + " a particular class,"
                  + " aborting the call");
         }
      }
      else
      {
         System.out.println(
            "Feature already loaded so "
               + "passing the "
               + "request on to it");

         /* Call doSomething on the feature, 
          * examining the args first to know 
          * how to pass it on
          */
         ((Feature) subject).doSomething(
            (String) joinPoint.getArgs()[0]);
      }
      return null;
   }
}

Finally, the TraceLazyInitialization aspect, as shown in the final code snippet below, merely provides a testing capability for the application to produce the output analyzed for the Summary and Conclusion to this article. This aspect merely examines each of the applications join points to provide an effective trace of when and where particular loading and initializations occurred to provide proof that lazy loading is indeed happening.

public aspect TraceLazyInstantiation 
{
   public pointcut lazyClassInitialized() : 
      initialization(
         features.FeatureA.new (..))
         || initialization(
               features.FeatureB.new (..));

   public pointcut cflowLazyInitialization() : 
      cflow(execution(MainApplication.new (..)))
      && !within(TraceLazyInstantiation);

   after() : lazyClassInitialized()
   {
      System.out.println(
         "*** Implementation class initialised: "
            + thisJoinPoint
               .getStaticPart()
               .getSignature()
            + " ***");
   }

   before() : cflowLazyInitialization()
   {
      System.out.println(thisJoinPoint);
   }
}

Summary and Conclusion

The example has shown that although the AspectJ version is certainly more complicated, it does have some significant advantages over the more traditional OO implementation of lazy loading. The most powerful advantage is that the lazy-loading logic is modularized and encapsulated within the LazyLoading aspect, rather than spread throughout the business logic classes. Therefore it can be amended and managed in a central location rather than the maintenance nightmare that could occur if changes had to be made to the mechanism in more traditional implementations.

An additional benefit is that to remove lazy loading would simply involve changing the initialization calls in the main method to initialize the concrete class instances directly. This would effectively remove the lazy-loading behavior should it not be needed with only a very simple change, rather than the fundamental architecture change that would be necessary in an OO-only system.

It is worth pointing out that there are several interesting features of AspectJ that have been included in this example that may confuse the beginner. AspectJ allows you to define default behavior on interfaces and this has been done in this case as part of the LazyLoading aspect affecting the Feature interface.

The default behavior leaves the interface as still being an interface, but also provides default behavior for specific areas of the interface to give the implementing classes the option of implementing that behavior or not.

In this example the LazyFeature did not need to implement the behavior for the doSomething(String) method since the message was only being used for interception by the lazy-loading aspect and so a default empty implementation was provided. However, the external feature developers would still be forced to implement the method since they would not be applying the LazyLoading aspect as part of their development, and therefore would only see the interface definition.

The apparent disparity between the default implementation of the doSomething(String) method being declared in the LazyLoading aspect could have been alleviated to some degree by declaring a static inner aspect in the Feature interface. However, this would have forced the implementers of the concrete features using the source code for the interface to use AspectJ to compile against the Feature interface. This constraint was avoided by placing the lazy-loading aspect specific extensions to the Feature interface in the aspect only.

In conclusion, although lazy loading can certainly be implemented effectively using traditional Object-Oriented methods only, the crosscutting nature of lazy loading is an ideal candidate for using an Aspect-Oriented approach, improving modularization in various fundamental ways.

Russell Miles is a senior consultant for SpringSource in the UK and contributes to various open source projects while working on books for O'Reilly.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.