Saturday, September 19, 2009

Annotation driven Caching with EhCache and Spring

Caching is the key of fast applications. The Spring modules framework provides us with an easy way to cache the return of our methods using java 5 annotations.

Here's an example :

ehCache.xml :

<ehcache>
<defaultCache
maxElementsInMemory="500"
eternal="true"
overflowToDisk="false"
memoryStoreEvictionPolicy="LFU" />

<cache name="getTestCache"
maxElementsInMemory="50"
eternal="true"
overflowToDisk="false"
memoryStoreEvictionPolicy="LFU" />
</ehcache>


applicationContext.xml :

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ehcache="http://www.springmodules.org/schema/ehcache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springmodules.org/schema/ehcache
http://www.springmodules.org/schema/cache/springmodules-ehcache.xsd">

<ehcache:config configLocation="classpath:ehcache.xml" />
<ehcache:annotations>
<ehcache:caching id="getTestCacheModel" cacheName="getTestCache" />
<ehcache:flushing id="getTestFlushModel" cacheNames="getTestCache" />
</ehcache:annotations>

<bean id="customerManager" class="services.impl.CustomerManagerImpl"/>

</beans>

The CustomerManager interface :

import org.springmodules.cache.annotations.Cacheable;
import org.springmodules.cache.annotations.CacheFlush;
import vo.Customer;

public interface CustomerManager {

@Cacheable(modelId="getTestCacheModel")
public Customer load(long customerId);

@CacheFlush(modelId="getTestFlushModel")
public void add(Customer customer);
}

In order to do a simple test, we create a simple implementation of the CustomerManager interface :

import services.CustomerManager;
import vo.Customer;

public class CustomerManagerImpl implements CustomerManager {

public Customer load(long customerId) {
//This part should normally call a DAO
return new Customer("Rene", 34);
}

public void add(Customer customer) {
//This part should normally call a DAO
}
}

And the test class :

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import services.CustomerManager;

public class Main {

public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
CustomerManager customerManager = (CustomerManager) context.getBean("customerManager");

System.err.println("Loading customer");
customerManager.load(455L);

System.err.println("Loading customer again");
customerManager.load(455L);

System.err.println("Adding customer");
customerManager.add(new Customer("Jean",34));
}
}

The final step is to add this simple log4j.properties file to the classpath :

# Set root category priority to INFO and its only appender to CONSOLE.
log4j.rootCategory=DEBUG, CONSOLE

# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=- %m%n

Here's the log result of the execution of this class :
Loading customer
- Attempt to retrieve a cache entry using key <1187678266|32099260> and cache model
- Retrieved cache element
- Attempt to store the object in the cache using key <1187678266|32099260> and model
- Object was successfully stored in the cache
Loading customer again
- Attempt to retrieve a cache entry using key <1187678266|32099260> and cache model
- Retrieved cache element
Adding customer
- Attempt to flush the cache using model
- Cache has been flushed.

We can see that the first time we call the load method, the retrieved Customer object is stored in the cache. The second time, the object is directly retrieved from the cache. When we call the add method the cache is flushed.

Here's the maven pom file I used :

We'll use the 0.8 version of springmodules, the latest in the maven repo central. This version has dependencies on spring 2.0 and ehcahe 1.1. In order to use the latest library we will add our own version of spring (2.5.6) and ehcache (1.6.2) and excludes the old ones. We have to exclude also all the proprietary libraries which should be optional but are not.

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
<version>2.5.6</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.springmodules</groupId>
<artifactId>spring-modules-cache</artifactId>
<version>0.8</version>
<exclusions>
<exclusion>
<artifactId>gigaspaces-ce</artifactId>
<groupId>gigaspaces</groupId>
</exclusion>
<exclusion>
<groupId>jini</groupId>
<artifactId>jsk-lib</artifactId>
</exclusion>
<exclusion>
<groupId>jini</groupId>
<artifactId>jsk-platform</artifactId>
</exclusion>
<exclusion>
<groupId>jini</groupId>
<artifactId>mahalo</artifactId>
</exclusion>
<exclusion>
<groupId>jini</groupId>
<artifactId>reggie</artifactId>
</exclusion>
<exclusion>
<groupId>jini</groupId>
<artifactId>start</artifactId>
</exclusion>
<exclusion>
<groupId>jini</groupId>
<artifactId>boot</artifactId>
</exclusion>
<exclusion>
<groupId>jini</groupId>
<artifactId>webster</artifactId>
</exclusion>
<exclusion>
<groupId>jboss</groupId>
<artifactId>jboss-cache</artifactId>
</exclusion>
<exclusion>
<groupId>jboss</groupId>
<artifactId>jboss-common</artifactId>
</exclusion>
<exclusion>
<groupId>jboss</groupId>
<artifactId>jboss-jmx</artifactId>
</exclusion>
<exclusion>
<groupId>jboss</groupId>
<artifactId>jboss-minimal</artifactId>
</exclusion>
<exclusion>
<groupId>jboss</groupId>
<artifactId>jboss-system</artifactId>
</exclusion>
<exclusion>
<groupId>jcs</groupId>
<artifactId>jcs</artifactId>
</exclusion>
<exclusion>
<groupId>xpp3</groupId>
<artifactId>xpp3_min</artifactId>
</exclusion>
<exclusion>
<groupId>ehcache</groupId>
<artifactId>ehcache</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework</groupId>
<artifactId>spring</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>


In conclusion, annotation driven Caching with EhCache and Spring is very straightforward to implement.

However, be aware that the springmodules framework is in a dead status and have some unresolved issues you should be aware of. You can see those issues listed here. Take a look at it before using it though most of the issues have workarounds. There is also a 0.9 version you can only download because it has never been released in the maven central repository.

A project named Spring modules fork has been created to revive this useful project and to make it evoluate. They've created a 0.10-SNAPSHOT version hosted on their own server.

11 comments:

  1. Nice post. The last time I checked, Spring Modules was still in Beta and I was little hesitant to use it in production.

    Here is another way to do the integration: http://blog.inflinx.com/?p=121

    ReplyDelete
  2. Why not just Ehcache DX management / tuning tools at http://ehcache.org? Designed by the core ehcache team.

    Full disclosure: I work on ehcache at terracotta.

    Cheers,

    --Ari

    ReplyDelete
  3. I like using annotations to configure my caching strategy. It's clear and simple. I didn't find any way to do this with ehcache only.

    ReplyDelete
  4. I am one of the authors of a new project intended to provide Ehcache integration for Spring 3 projects via annotations:

    http://code.google.com/p/ehcache-spring-annotations/

    We are excited to announce the general availability of the first production release, 1.0.1.

    This release provides 2 method-level annotations in the spirit of Spring’s @Transactional:

    @Cacheable
    @TriggersRemove

    When appropriately configured in your Spring application, this project will create caching aspects at runtime around your @Cacheable annotated methods.

    Usage documentation can be found on the project wiki:

    http://code.google.com/p/ehcache-spring-annotations/wiki/UsingCacheAnnotations
    http://code.google.com/p/ehcache-spring-annotations/wiki/UsingTriggersRemove

    ReplyDelete
  5. Thanks. I will definitively take a look at your project.

    ReplyDelete
  6. It also addresses the issues with consistent key generation that you address in another blog post.

    ReplyDelete
  7. For some reasons application context did not come in previous post. Please have it below.

    Thanks,
    Pawan

    Application Context . xml
    =========================

    ReplyDelete
  8. Be aware that Spring Modules and Spring Modules Fork are currently not compatible with spring 3.0. If you really want to upgrade to spring 3.0, use the solution described above by Eric.

    ReplyDelete
  9. i am trying to work with Spring 3.1 m1 cache abstraction -
    but, getting this ERROR :
    Error creating bean with name 'org.springframework.cache.interceptor.CacheInterceptor#0'

    Please help me out!!

    ReplyDelete
  10. great post; this means i can prob get out of here before midnight. if you were in the office, i'd shout the beers. thanks!

    ReplyDelete