Simple caching with Spring AOP

September 18, 2009 1 comment

It’s recommended to read Simple caching with AspectJ before you continue. Only difference is in using Spring AOP.
At first we need to create spring-config.xml and define Spring beans and enable Spring AOP autoproxy for AspectJ:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

	<aop:aspectj-autoproxy />
	<bean class="caching.springaop.CacheAspect" />
	
	<bean id="calc" class="caching.springaop.Calculator" />

</beans>

Now we can use Calculator bean created by Spring:

ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-config.xml");
Calculator calc = (Calculator) ctx.getBean("calc");

// result will be calculated and stored in cache
logger.info("1 + 2 = " + calc.sum(1, 2));

// result will be retrieved from cache
logger.info("1 + 2 = " + calc.sum(1, 2));

Download full source code as a zip or tar.gz file or browse code online on GitHub.

Simple caching with AspectJ

September 6, 2009 4 comments

Caching hasn’t been never so easy to implement before. AspectJ is one of the best (probably the best) technologies that can be used for implementing caching system.

There are several ways how to do caching. Object can directly set values to cache, can implement some caching interface or can be fancy and use annotations that sounds to me as a most elegant solution. With annotation you can annotate any method that should be cached. Could be something easier?

Let’s create @Cacheable annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Cacheable {

}

With this annotation we can mark any expensive, time consuming methods whose results should be cached. For example let’s create Calculator class that has one very expensive and time consuming method. Every time we call this method with same input it will be executed. It’s a huge wasting of resources. So we will use our @Cacheable annotation on it’s function to prevent executing it when it’s not necessary.

public class Calculator {

	private Logger logger = Logger.getLogger(Calculator.class);

	@Cacheable
	public int sum(int a, int b) {
		logger.info("Calculating " + a + " + " + b);
		try {
			// pretend this is an expensive operation
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			logger.error("Something went wrong...", e);
		}
		return a + b;
	}

}

Next we need to create an aspect called CacheAspect that will handle caching. In this aspect we define pointcut that matches all methods annotated with @Cacheable, regardless of number and type of input arguments and return type:

@Pointcut("execution(@Cacheable * *.*(..))")
private void cache() {}

It is a best practice to annotate empty methods with a pointcut. Function cache() can be considered as a name for a pointcut.
As a last step we need to create an advice – a function that wraps calling of cacheable methods. This function will be actually called every time empty function cache() is invoked. Advise aroundCachedMethods will take a look into cache for a previously cached result and return it. If it couldn’t find anything then it will call our time consuming function and store result for next reuse.

@Around("cache()")
public Object aroundCachedMethods(ProceedingJoinPoint thisJoinPoint)
		throws Throwable {
	logger.debug("Execution of Cacheable method catched");

	// generate the key under which cached value is stored
	// will look like caching.aspectj.Calculator.sum(Integer=1;Integer=2;)
	StringBuilder keyBuff = new StringBuilder();

	// append name of the class
	keyBuff.append(thisJoinPoint.getTarget().getClass().getName());

	// append name of the method
	keyBuff.append(".").append(thisJoinPoint.getSignature().getName());

	keyBuff.append("(");
	// loop through cacheable method arguments
	for (final Object arg : thisJoinPoint.getArgs()) {
		// append argument type and value
		keyBuff.append(arg.getClass().getSimpleName() + "=" + arg + ";");
	}
	keyBuff.append(")");
	String key = keyBuff.toString();

	logger.debug("Key = " + key);
	Object result = cache.get(key);
	if (result == null) {
		logger.debug("Result not yet cached. Must be calculated...");
		result = thisJoinPoint.proceed();
		logger.info("Storing calculated value '" + result + "' to cache");
		cache.put(key, result);
	} else {
		logger.debug("Result '" + result + "' was found in cache");
	}

	return result;
}

Download full source code as a zip or tar.gz file or browse code online on GitHub.

Categories: Java Tags: , , ,