Showing posts with label AspectJ. Show all posts
Showing posts with label AspectJ. Show all posts

Thursday, May 15, 2008

AspectJ: A crucial distinction between execution and call join points

Given the following code:


class Super {
protected void m() {}
}
class Sub extends Super {}


A call( * Sub+.m( .. ) ) join point will match any call to m made on an instance statically known to be a Sub (in particular, ( ( Super )s ).m() will not only not match, but will cause a compiler warning); an execution( * Sub+.m() ) join point will not match s.m(), even if s is Sub, because there's no m() code to execute in Sub!

Spring declarative transactions, applied dynamically

I think declarative transaction management is possibly the most compelling reason for using the Spring Framework. I won't explain how it usually works, leaving that to the Spring docs and plenty of examples in the literature. Recently I ran into the following conundrum, however.

I have a service class that has not only some "transactional" methods, but also other methods that return instances of secondary classes with their own transactional methods. It is easy in the configuration of the Spring container to declare the transactional methods in the service as such, because it is a typical singleton bean. The problem is that if the code in the service implementation is to remain decoupled from Spring, how do we instruct Spring to wrap transactional proxies around any instances of the secondary classes that are produced at runtime?

My first attempt to solve this problem was to factor the code that needed to be transactional out of the secondary classes into package-access methods on the main service, in the hopes that I could declare those helper methods transactional in the configuration. Then the formerly transactional methods in the secondary classes could call those helper methods back in the service bean. This attempt proved to be not just inelegant, but also ineffectual: a complete waste of time.

Why didn't it work? Because the newly-minted secondary class instances were not receiving a reference to the service bean's transactional proxy to make their call backs on; they got the unadorned service instance as obtained via this, and in order to get the proxy, the Spring coupling would have to creep back into the code. Only a more complete AOP framework like AspectJ, with its special compiler, could avoid the this problem.

My solution to this problem was in four parts. Spring 2.5 is required.


  1. Create a new implementation of each secondary class which is a pure delegating proxy. The constructor of DelegatingSecondaryClassImpl takes a SecondaryClass as delegate, and every method simply forwards to that delegate.

  2. Create a BeanFactoryAware aspect ServiceAdvice with around advice for the methods on the service class that produce instances of these secondary classes, one advice method per secondary class. In each case the advice looks like this:

    public SecondaryClass secondaryClassAroundAdvice( ProceedingJoinPoint pjp ) throws Throwable {
    return ( SecondaryClass )bf.getBean( "secondaryClassBeanId", new Object[] { pjp.proceed() } );
    }


  3. Add a prototype bean to the configuration XML for each secondary class:

    <bean id="secondaryClassBeanId" class="com.amazon.foo.bar.DelegatingSecondaryClassImpl" scope="prototype">
    <constructor-arg><null/></constructor-arg>
    </bean>


  4. Add advice to the service methods that produce instances of each secondary class in the configuration XML:

    <bean id="serviceAdvice" class="com.amazon.foo.bar.ServiceAdvice">
    <aop:config>
    <!-- transactional advisor goes here ... -->
    <aop:aspect id="proxyAspect" ref="proxyAdvice">
    <aop:around pointcut="execution( com.amazon.foo.bar.SecondaryClass createSecondaryClass1( .. ) ) || execution( com.amazon.foo.bar.SecondaryClass createSecondaryClass2( .. ) )" method="secondaryClassAroundAdvice">
    </aop:around>
    </aop:aspect>
    </aop:config>


Friday, August 03, 2007

AspectJ for lazy loading, improved

I find Russ Miles' lazy feature loading recipe (AspectJ Cookbook, chapter 21.3) cumbersome and at odds with the whole point of AOP. You have to use joinPoint.getArgs inside a proxy; but you're using AspectJ instead of Spring to avoid the proxy-based Spring AOP implementation, with all its intrinsic problems (what does "this" mean?). You also have to be aware in the mainline code that you're doing lazy loading and explicitly call LazyLoading.aspectOf().initializeFeature(), so it's intrusive.

In addition, there is essentially no reusability here. You have to create a special aspect for each interface which you wish to load lazily, with stubs for every method of that interface.

Finally, you have to use a weird trick of declaring that one interface implements another without actually giving any implementation (this line is given with no explanation in the text).

My approach, by contrast, is extremely simple. I do still impose a burden on the mainline code; specifically, if you want to see any benefit from lazy loading of an object you have to make sure the expensive initialization code actually occurs within the construction of that object. But that's it. No proxies, no hard-coded interface stubs, no intrusion.

The basic technique is to suppress the execution of the object's initialization code, wrapping it in a closure and storing it away until the first access to the object that requires that the initialization take place. The storage of the closure, and the state of the flag indicating that the initialization has or has not been attempted, live inside a small pertarget aspect.

Here's two variations of the LazyInitialization aspect.

The first:

// one aspect instance per lazily-instantiated object
public aspect LazyInitialization pertarget( target( Lazy ) ) {

// this interface represents a closure for the real initialization
private interface DelayedInit {
 void init();
}

// this is the marker interface for types that should be
// lazily initialized
public interface Lazy {};

 // per-object aspect state
 boolean flgTried = false;
 DelayedInit fnInit = null;

 // this advice intercepts the normal initialization process for
 // a Lazy object and stashes a closure for it in the aspect
 void around() : execution( Lazy+.new( .. ) ) {
  synchronized( this ) {
   if ( fnInit == null )
    fnInit = new DelayedInit() {
     public void init() {
      proceed();
     }
    };
  }
 }

  // this advice intercepts any access to a Lazy object that might
  // require it to actually initialize, and does so if necessary
  before() : call( * Lazy+.*( .. ) ) || get( * Lazy+.* ) || set( * Lazy+.* ) {
   synchronized( this ) {
    if ( !flgTried ) {
     flgTried = true;
     fnInit.init();
    }
   }
  }
}

To use it, just declare somewhere that some class or interface C implements LazyInitialization.Lazy and every instance of C or its subclasses/implementations will be lazily initialized:

 
aspect MakeCLazy {
   declare parents : C+ implements LazyInitialization.Lazy;
}

The other variation uses a generic type variable T instead of a marker interface. This version allows you to be more specific about which classes/interfaces/methods are to be lazily initialized and what constitutes an initialization-worthy access via filter pointcuts.

public abstract aspect GenericLazyInit< T > pertarget( initFilter() ) {

 // this interface represents a closure for the real initialization
 private interface DelayedInit {
  void init();
 }

 // per-object aspect state
 boolean flgTried = false;
 DelayedInit fnInit = null;

 protected pointcut initFilter() : execution( T+.new( .. ) );
 protected pointcut accessFilter() : call( * T+.*( .. ) ) || get( * T+.* ) || set( * T+.* );
 
 // this advice intercepts the normal initialization process for
 // a Lazy object and stashes a closure for it in the aspect
 void around() : initFilter() {
  synchronized( this ) {
   if ( fnInit == null )
    fnInit = new DelayedInit() {
     public void init() {
      proceed();
     }
    };
  }
 }

 // this advice intercepts any access to a Lazy object that might
 // require it to actually initialize, and does so if necessary
 before() : accessFilter() {
  synchronized( this ) {
   if ( !flgTried && fnInit != null ) {
    flgTried = true;
    fnInit.init();
   }
  }
 }
}

Using this version (in its default configuration, equivalent to variation 1) is even briefer:

 
aspect MakeCLazy extends GenericLazyInit< C > {}