Practical caching with Spring AOP
August 17, 2010 § 1 Comment
In this post I will discuss a practical application of Spring AOP: Caching. Most tutorials/introductions to Spring AOP use logging to motivate the usage of Aspect Oriented Programming. While systematic logging is a valid concern, I am not quite sure that it would alone justify the introduction of AOP into your project. The AOP technique has, after all, serious implications:
- Maintenance/Code readability: While AOP can help reduce the clutter in the code by taking away repetitive statements, it can, by the same token, reduce the maintainability of your code (how well defined are your pointcuts?)
- Performance: Beans are typically proxied in Spring and this does have a negative performance impact
A strong argument can be made in favor of Spring AOP when some client requests need to be intercepted programmatically and returned from cache instead of being processed. This case seems to lend itself to an Around Advice where the request is inspected and a decision can be made on whether to produce the response from cache or fully compute it. This approach assumes, of course, that most requests are cache-candidates. One advantage of this approach is its non-intrusive approach: The code computing the response remains untouched.
I will take a very pragmatic approach by showing you the main steps in building the solution; you will see first hand why this problem fits so “naturally” within the realm of AOP. Any other approach would require an intercept of some kind, so you might as well use a solid third-party framework.
The first step would be to identify those methods in your code where the expensive processing of requests takes place. This step will in turn enable you to properly define your join points. Do note that Spring AOP allows you to define join points on methods only. You can then define in a declarative fashion the pointcuts matching those join points (think of poincuts as the embodiment of join points at run time). The proper definition of join points is actually crucial since all subsequent calls to the identified methods will be advised!
I will use Spring 2.5 Annotations in this post, so the pointcut definition could be expressed as an annotation in your Advice class (I will get to the Advice class in a moment):
"execution (com.mypackage.MyClass.myMethod(..)) " + "&& args(request)"
In this example we have identified the method where the expensive processing of requests takes place to be myMethod() in class MyClass under the com.mypackage package. The pointcut designator is “execution”, as opposed to, say, “within”, “targets’”, etc… This choice of the “execution” pointcut designator is a practical one: It is the most common designator and it happens to suit this type of application.
The next step is to implement the Around Advice as a Spring 2.5 annotated Java class. I am listing a skeleton class that illustrates the main ideas:
- The class contains a cache attribute that gets injected by Spring
- The main method is called: doEvaluteExpensiveMethod(). Its first argument must be of type ProceedingJoinPoint and it must return an Object type.
- The call to pjp.proceed() is optional in Spring AOP but in this case it makes sense to call it
- Exception Handlind is very important here since a failure can alter the program flow
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Required;
@Aspect
public class AroundExpensiveProcessingAdvice {
private SomeCache cache;
/**
* @param pjp
* @param request
* @return Object
* @throws Throwable
*/
@Around(value = execution (com.mypackage.MyClass.myMethod(..)) " +
"&& args(request),
argNames = "pjp, arg0")
public Object doEvaluteExpensiveMethod(ProceedingJoinPoint pjp, Map<String, String> request) throws Throwable {
ClientResponse result = null;
try {
// if request contains a specific key/value populate result from cache
if (cache.containsKey(key) {
result = cache.get(key);
}
} catch (Exception e) {
return pjp.proceed();
}
if (result == null) {
return pjp.proceed();
}
return result;
}
/**
* @param cache the cache to set
*/
@Required
public void setCache(SomeCache cache) {
this.cache = cache;
}
}
Finally the Advice must be Spring configured; I choose to use a Spring XML context file as follows:
<?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/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"
default-lazy-init="true">
<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean id="my.service.QueryCache"
class="wrapper.around.popular.cache.library"
factory-method="getInstance" />
<bean id="my.service.AroundExpensiveProcessingAdvice" class="AroundExpensiveProcessingAdvice">
<property name="cache" ref="my.service.QueryCache" />
</bean>
</beans>
A couple of notes about this context XML configuration file:
- In order to use <aop:aspectj-autoproxy> element you must include references to the Spring AOP schema file
- The proxy-target-class=”true” attribute is included just in case the target class of the Around Advice (com.mypackage.MyClass) is not an Interface, otherwise you can skip it
- The call to pjp.proceed() is optional in Spring AOP but in this case it makes sense to call it
That’s pretty much it; you can extend the example by using a Pointcut that applies to more than one method across more than one package etc… The main idea is to keep the caching aspect separate from the main processing code. The minute you want to stop advising the selected method(s) you can simply remove the bean entry corresponding to the Around Advice from the Spring XML context file.