Friday, November 12, 2010

Dynamic proxy vs. AOP - simple cache

I'm not going to explain what dynamic proxy (introduced in Java 1.3) and Aspect Oriented Programming (AspectJ implementation) are, as there's a lot of places on the Internet where you can find information about both. I'll rather focus on how to use these two technologies to apply some basic cache mechanism to our project - which will be a simple calculator. Dynamic Proxy
As Dynamic Proxy requires proxied class to implement at least one interface, we will start from creating one:

package net.progsign.proxy;

public interface Calculator {
 public long factorial(int n);
}
and the implementation:
package net.progsign.proxy;

public class CalculatorImpl implements Calculator {
 
 @Override
 public long factorial(int n) {
  return n==0 ? 1 : n * factorial(n-1);
 }
}

Nothing special in the above code snippets. Next, the main part, our InvocationHandler, which will be passed as a parameter to the Proxy.newProxyInstance(InvocationHandler) method. It may look like this:

package net.progsign.proxy.dynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class CalculatorCacheHandler implements InvocationHandler {
 private static final Log log = LogFactory.getLog(CalculatorCacheHandler.class);
 private Map<Integer, Long> cache;
 private Object target;
 
 public CalculatorCacheHandler(Object target) {
  cache = new HashMap<Integer, Long>();
  this.target = target;
 }
 
 @Override
 public Object invoke(Object proxy, Method method, Object[] args)
   throws Throwable {
  int value = (Integer)args[0];
  long computed = 0;
  if(cache.containsKey(value)) {
   log.debug(String.format("[proxy] - hit against cache (%d)", value));
   computed = cache.get(value);
  } else {
   computed = (Long) method.invoke(target, args);
   cache.put(value, computed);
   log.debug(String.format(
    "[proxy] - cached new result (%d/%d)", value, computed));
  }
  return computed;
 }
}

Code explanation: our cache is implemented as a simple map which matches a value to a computed result. If the cache has already got a result for the value that was passed to our method, we simply return that result. Otherwise, if the result hasn't been computed yet, we do it now by invoking calculator's method, then cache the result and finally return it to user. Test application:

package net.progsign.proxy.sample;

import java.lang.reflect.Proxy;

import net.progsign.proxy.Calculator;
import net.progsign.proxy.dynamic.CalculatorCacheHandler;
import net.progsign.proxy.dynamic.CalculatorProxy;

public class ProxyTest {
 public static final void main(String[] args) {
  CalculatorProxy calculatorProxy = new CalculatorProxy();
  Calculator proxy = (Calculator)Proxy.newProxyInstance(
    calculatorProxy.getClass().getClassLoader(),
    calculatorProxy.getClass().getInterfaces(),
    new CalculatorCacheHandler(calculatorProxy));
  calculatorProxy.setProxy(proxy);
  
  System.out.println(proxy.factorial(4));
  System.out.println(proxy.factorial(7));
 }
}
Aspect Oriented Programming (AspectJ)

If Eclipse is your IDE, there are two methods you can incorporate AOP to your project. You can either install AspectJ plug-in for Eclipse (i.e. "Eclipse AspectJ Development Tools") or you can simply add required libraries to your project, configure AspectJ through XML configuration file and finally instruct JVM to use java agent, provided with AspectJ, to weave in your aspects. I'll use the second approach in this example. We start from the Calculator class (notice it doesn't implement any interface):

package net.progsign.proxy;

public class Calculator {
 
 public long factorial(int n) {
  return n==0 ? 1 : n * factorial(n-1);
 }
}
And here's the AOP part:
package net.progsign.proxy.aop;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

@Aspect
public class CalculatorCacheAspect {
 private static final Log log = LogFactory.getLog(CalculatorCacheAspect.class);
 private Map<Integer, Long> cache;
 
 public CalculatorCacheAspect() {
  cache = new HashMap<Integer, Long>();
 }

 @Pointcut("call(public * net.progsign.proxy.Calculator.factorial(int)) && args(value)")
 public void cacheCheckPointcut(int value) {}
 
 @Around("cacheCheckPointcut(value)")
 public Object wrapCalculation(ProceedingJoinPoint pjp, int value) throws Throwable {
  if(cache.containsKey(value)) {
   log.debug(String.format("[aspect] - hit against cache (%d)", value));
   return cache.get(value);
  }
  return pjp.proceed();
 }
 
 @AfterReturning(value="cacheCheckPointcut(value)", returning="result")
 public void updateCache(int value, long result) {
  cache.put(value, result);
  log.debug(String.format("[aspect] - cached new result (%d/%d)", value, result));
 }
}

In the above code we have one pointcut which translates to every call to public method Calculator.factorial(int) which accepts one parameter of type int. We will use value of this parameter later on in our example. Next, we define two advices:

  • public Object wrapCalculation(ProceedingJoinPoint pjp, int value) - this advice is executed around the method invocation. We first check if value that was passed as an argument to our woven method already exists in the cache. If it does, we simply return pre-calculated result from the cache. Otherwise we let the calculator's method to execute and return whatever that method returns.
    Here, the ProceedingJoinPoint is a reference to our join point. By invoking the proceed() method we say, that the actual method that was advised, which in this case is Calculator.factorial(int value), should be executed. The second parameter is the parameter defined in the pointcut and it refers to the argument of the Calculator.factorial(int value) method.
  • public void updateCache(int value, long result) - this advice method is invoked after successfully returning from our pointcut. We take both, value passed as an argument to the advised method and the result it returns (no matter whether it was computed or taken directly from the cache) and put this pair in to our cache.

The last thing is the configuration XML for the application (META-INF/aop.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE aspectj PUBLIC
"-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
 <aspects>
  <aspect name="net.progsign.proxy.aop.CalculatorCacheAspect"/>
 </aspects>
 <weaver option="-verbose">
  <include within="net.progsign.proxy..*"/>
 </weaver>
</aspectj>

I only defined one aspect, by specifying its fully qualified name, net.progsign.proxy.aop.CalculatorcacheAspect and joint points in the application where the aspect will be woven in, which in this case is all classes within package net.progsign.proxy and all its sub-packages. The test application is as simple as:

package net.progsign.proxy.sample;

import net.progsign.proxy.Calculator;

public class ProxyTest {
 public static final void main(String[] args) {
  Calculator impl = new Calculator();
  System.out.println(impl.factorial(4));
  System.out.println(impl.factorial(7));
 }
}

There's one last thing to do. To weave in the aspect to our project we also have to instruct JVM to use AspectJ agent:

-javaagent:${project_loc}/lib/aspectjweaver.jar
Conclusions

As you probably already know both Dynamic Proxy and AOP are widely used in many frameworks, e.g. IoC in Spring Framework or logging purposes in business applications. Dynamic Proxy is not very hard to implement in simple applications, but may be cumbersome in more complex systems. Sometimes even impossible. For example, there's no simple way to create dynamic proxy for recursive methods. Such method has to call its proxied version, thus be proxy aware and this may lead to design issues.
Another disadvantage is the way we implement it in our project. All methods must be called through proxy instance in order to being intercepted by the InvocationHandler and this means we have to modify our code. This, of course, may be time consuming and error prone. AOP lets us avoid these problems, because it doesn't affect the source code directly. It weaves in the new functionality into byte code and switching it on and off can be as easy as:

<aspectj>
 <weaver option="-verbose">
  <include within="net.progsign.proxy..*"/>
  <exclude within="*" />
 </weaver>
</aspectj>

Another advantage of AOP is that our classes don't have to implement any interfaces. We just choose which joint points we want to advise. It can be method calls, method executions, class attribute access, constructors, etc. Both Dynamic Proxy and AOP may be a good solution in real projects, although the latter one is really worth giving a try.