Simple caching with AspectJ
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.
Very good post. I am developing a timing aspect for my application. This has been very useful for me.
Thank you.
thanks for this…. it is right what I need.
I really speculate the reasons why you labeled this specific
blog post, “Simple caching with AspectJ
My web and Java snippets”. Either way I personally
admired it!Thank you-Winston