AOP Method Caching Using AspectJ and Annotations

A very quick and easy way to do data access caching within your application would be to use annotations and AOP. Essentially, you will be tagging methods for caching with annotations. Aspect oriented design fits here since you should really be utilizing AOP for certain aspects such as security, logging, auditing, or in this example, caching.

A simple annotation defined below is all you need to define. You can then pick and choose the methods that you want to cache. Instead of just caching ALL service/persistence functions for data access, it is best to give developer the control to pick and choose which functions to cache. Also, annotating a method in a ClearCache can signify a function that may be changing data in which the cache should be cleared.

public @interface Cacheable {
	 * caches the object at the sublcass level instead of at the superclass level
	 * @author David Reepmeyer
	 * @return 
	boolean subclass() default true; 
	 * caches the object based on the arguments sent to the function.  
	 * It maps by function name + arguments
	 * @author David Reepmeyer
	 * @return
	boolean arguments() default true;
public @interface ClearCache {

Next we can annotate the methods that you want to define.

public class SomeServiceImpl1 implements SomeService {
	public List<String> getTypes() {  
		// get types
	@Cacheable(subclass=true, arguements=true)
	public List<String> getTypes(Object someArg) {  
		// get types
	public List<SelectItem> getItems() {  
		// get items
	public List<SelectItem> saveItems(List<Items> items) {  
		// save items to database

Now for the meat of the work we will use AspectJ for catching the @Cachable annotations.

public class CachingAspect {
    private static org.apache.log4j.Logger log = Logger.getLogger(CachingAspect.class);
	private Map<String, Object> cache = new HashMap<String, Object>();
	private boolean enable = true;
	@Around("execution(@Cacheable * *.*(..))  && @annotation(cacheAnnotation)")
	public Object cache(ProceedingJoinPoint pjp, Cacheable cacheAnnotation) throws Throwable {
		if (!enable) {
			log.info("Caching is now disabled.");
			return pjp.proceed();
		String key = getKey(pjp, cacheAnnotation.subclass(), cacheAnnotation.arguments());
		if (!cache.containsKey(key)) {
			log.info("Calling method and caching: " + key);
			cache.put(key, pjp.proceed());
		} else {
			log.info("Retrieving cached result from key: " + key);
		return cache.get(key);
	@Around("execution(@ClearCache * *.*(..))")
	public Object clearCache(ProceedingJoinPoint pjp) throws Throwable {
		this.enable = false;
		cache = new HashMap<String, Object>();
		Object result = pjp.proceed();
		return result;
	private String getKey(ProceedingJoinPoint pjp, boolean subclass, boolean arguements){
		String fullClassname = getClassName(pjp, subclass);	
		String method = pjp.getSignature().getName();
		String key = fullClassname + "." + method;
		if (arguements)
			key = key + "-" + getArguementsString(pjp);
		return key;
	private String getClassName(JoinPoint jp, boolean subclass) {
		if (!subclass)
			return jp.getTarget().getClass().getCanonicalName();
			return jp.getSignature().getDeclaringTypeName();
	private String getArguementsString(JoinPoint jp) {
		StringBuffer args = new StringBuffer();
		for (Object o :jp.getArgs()) 
		return args.toString();

As you can see above, a Map is used to store the cached objects and the key is made up of the package name + class name + method name. This makes it unique for that method. If the arguments needed to be used as part of the unique key, then the annotation will specify this.

The subclass variable will determine if the cached object should be stored by using any subclasses as part of the key. If it is false, it will use the current class that contains the annotation.

The arguments variable will allow to cache the object based on the class/method name as the key, as well as the values of the arguments that are passed into it.

AspectJ and annotations can both act as a pre-processing technique. As some instances they appear to compete to do the same type of work, here it is apparent that the complement one another. The pointcut will be defined at all of the Cacheable/ClearCache annotations and the advice will be given by the CachingAspect class.

This allows developers to code for a generic aspect of work, while also quickly allowing developers to define where the specifics should be applied via annotations.

Distributed Caching and Integration with Existing Caching Systems

There are several existing caching systems that exist today. EhCache, OSCache, JBossCache, JCS, SwarmCache and JCache just to name the most popular. As you can see in this example, the CachingAspect aspect simply uses a HashMap to store data. To utilize one of these existing frameworks, just simply replace the HashMap. If you are running this in a clustered environment then this will be necessary. This will allow for distributed caching across multiple JVM's. For example, if you choose to utilize EHCache, then distributed caching can by accomplished by either RMI replication, JMS replication, or JGroups replication. It would be up to the chosen caching framework.

Post new comment

Subscribe to Syndicate