Quick start with Ehcache Annotations for Spring

How to profit from the new Ehcache Annotations for Spring? As a Java developer you would like to know, don’t you? ehcache-spring-annotations – licensed under the Apache License, Version 2.0 – is a project that simplifies caching in Spring based application using popular Ehcache library. Version 1.1.2 of ehcache-spring-annotations has been released only recently. In this article I will present how to create a web project that benefits from ehcache-spring-annotations

Create a new web project

For the showcase, a Spring MVC based web project will be used. If you use Eclipse make sure you have m2eclipse installed, then create a Maven project using JEE 5 web application archetype (group id: org.codehaus.mojo.archetypes, artifact id: webapp-jee5): wtp The above archetype works perfectly with Eclipse 3.6.

Project structure

I created a simple web project that consists of one controller: MessageController:

[java]
@Controller
public class MessageController {

	@Autowired(required = true)
	private MessageStorage messageStorage;

	public MessageController(MessageStorage messageStorage) {
		super();
		this.messageStorage = messageStorage;
	}

	public MessageController() {

	}

	@RequestMapping(method = RequestMethod.GET, value = "/message/add")
	public ModelAndView messageForm() {
		return new ModelAndView("message-form", "command", new Message());
	}

	@RequestMapping(method = RequestMethod.POST, value = "/message/add")
	public ModelAndView addMessage(@ModelAttribute Message message) {
		messageStorage.addMessage(message);
		return getMessageById(message.getId());
	}

	@RequestMapping(method = RequestMethod.GET, value = "/message/{id}")
	public ModelAndView getMessageById(@PathVariable("id") long id) {
		Message message = messageStorage.findMessage(id);
		ModelAndView mav = new ModelAndView("message-details");
		mav.addObject("message", message);
		return mav;
	}

	@RequestMapping(method = RequestMethod.GET, value = "/message")
	public ModelAndView getAllMessages() {
		Collection<Message> messages = messageStorage.findAllMessages();
		ModelAndView mav = new ModelAndView("messages");
		mav.addObject("messages", new CollectionOfElements(messages));
		return mav;
	}
}
[/java]

The above controller depends on MessageStorage, simple DAO, that is defined as follows:

[java]
public interface MessageStorage {
	Message findMessage(long id);
	Collection<Message> findAllMessages();
	void addMessage(Message message);
}
[/java]

The only implementation of MessageStorage is MemoryMessageStorage:

[java]
@Component
public class MemoryMessageStorage implements MessageStorage {

	private Map<Long, Message> messages;

	private AtomicLong newID;

	public MemoryMessageStorage() {
		// ...

		// initialize some messages
		addMessage(new Message("user:1", "content-1"));
		addMessage(new Message("user:2", "content-2"));
		addMessage(new Message("user:3", "content-3"));
		addMessage(new Message("user:4", "content-4"));
		addMessage(new Message("user:5", "content-5"));
	}

	@Override
	public Message findMessage(long id) {
		// ...
	}

	@Override
	public Collection<Message> findAllMessages() {
		// ...
	}

	@Override
	public void addMessage(Message message) {
		// ...
	}
}
[/java]

Whit the minimum dependencies presented below we are able to run the application (See Download section to get complete application).

[xml]
    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>3.0.3.RELEASE</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-oxm</artifactId>
            <version>3.0.3.RELEASE</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
    </dependencies>
[/xml]

Introducing Ehcache Annotations for Spring to the web project

Now it’s time to add caching capabilities to the application. We provide caching to MemoryMessageStorage. First of all, add the required dependencies to the POM: Ehcache Annotations for Spring dependency:

[xml]
        <dependency>
            <groupId>com.googlecode.ehcache-spring-annotations</groupId>
            <artifactId>ehcache-spring-annotations</artifactId>
            <version>1.1.2</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
[/xml]

As of time of writing there was version 2.2.0 of Ehcache available. But we use version 2.1.0 that Ehcache Annotations for Spring 1.1.2 depends on. I also added SLF API implementation:

[xml]
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.6.1</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
[/xml]

With the above dependencies we are able to use Ehcache Annotations for Spring. We add the annotations to MemoryMessageStorage as mentioned before. There are a few simple rules:

  • use cache with name “messageCache” for caching messages for invocations of findMessage(long)
  • use cache with name “messagesCache” for caching messages for invocations of findAllMessages()
  • remove all elements from “messagesCache” after the invocation of addMessage(Message)

To achieve the above we use @Cachable and @TriggersRemove annotations aspresented below:

[java]
@Component
public class MemoryMessageStorage implements MessageStorage {

	private Map<Long, Message> messages;

	private AtomicLong newID;

	public MemoryMessageStorage() {
		// ...
	}

	@Override
	@Cacheable(cacheName = "messageCache")
	public Message findMessage(long id) {
		// ...
	}

	@Override
	@Cacheable(cacheName = "messagesCache")
	public Collection<Message> findAllMessages() {
		// ...
	}

	@Override
	@TriggersRemove(cacheName = "messagesCache", when = When.AFTER_METHOD_INVOCATION, removeAll = true)
	public void addMessage(Message message) {
		// ...
	}
}
[/java]

Spring and Ehcache configuration

Having annotations in place, we need to configure the application to use them. This is done via Spring configuration file:

[xml]
<?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:context="http://www.springframework.org/schema/context"
	xmlns:oxm="http://www.springframework.org/schema/oxm"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:ehcache="http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
		http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.0.xsd
		http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
        http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring/ehcache-spring-1.1.xsd">

    <ehcache:annotation-driven />

    <ehcache:config cache-manager="cacheManager">
        <ehcache:evict-expired-elements interval="60" />
    </ehcache:config>

    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>

    <!-- rest of the file omitted -->

</beans>

[/xml]

The last thing to do is to add the Ehcache configuration. Create file named ehcache.xml and place it in /WEB-INF folder of web application:

[xml]
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
    <defaultCache eternal="true" maxElementsInMemory="100" overflowToDisk="false" />
    <cache name="messageCache" maxElementsInMemory="10" eternal="true" overflowToDisk="false" />
    <cache name="messagesCache" maxElementsInMemory="10" eternal="true" overflowToDisk="false" />
</ehcache>
[/xml]

Configure cache manager so that it uses Ehcache configuration. To do so, add configLocation property to cacheManager bean in Spring context file:

[xml]
    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation"  value="/WEB-INF/ehcache.xml"/>
    </bean>
[/xml]

The configuration is done. Run the application on Tomcat 6 server (Run As – Run on server) and observe log output – if DEBUG is enabled you will find similar entries: DEBUG [net.sf.ehcache.Cache]: Initialised cache: messagesCache DEBUG [net.sf.ehcache.Cache]: Initialised cache: messageCache Do some interactions with the application to observe the caching behavior (make sure DEBUG log level is enabled). The actions are (to see XML output replace “html” with “xml”):

  • get the list of all messages – http://localhost:8080/esa/message.html
  • get a message by id – http://localhost:8080/esa/message/{id}.html
  • add a new message (from) – http://localhost:8080/esa/message/add.html

If you execute getMessages() of MessageController for the first time you should see: DEBUG [com.googlecode.ehcache.annotations.interceptor.EhCacheInterceptor]: Generated key '167334053963' for invocation: ReflectiveMethodInvocation: public abstract java.util.Collection com.goyello.esa.storage.MessageStorage.findAllMessages(); target is of class [com.goyello.esa.storage.MemoryMessageStorage] DEBUG [com.goyello.esa.storage.MemoryMessageStorage]: == got all messages, size=5 While invoking the same method for the second time, the second line from the above log output is gone, because the collection of all messages was retrieved from the cache. To remove cache from the messages in a simple way call addMessage() of MessageController. Then repeat previous steps to make sure the cache was cleared properly.

Unit testing

To be sure that the caching works without reading the log files we create unit tests for caching. For this tutorial we modify the MessageStorage interface and we add void setDelegate(MessageStorage storageDelegate) method to it. The given delegate will be used to check if the caching actually works. The implementation class changes are (for all other methods similarly):

[java]
	@Override
	@Cacheable(cacheName = "messageCache")
	public Message findMessage(long id) {
		LOG.debug("== find message by id={}", id);
		if(storageDelegate != null)
			storageDelegate.findMessage(id);
		return messages.get(id);
	}
[/java]

To make the testing easier we use two more dependencies: Spring Test and Mockito:

[xml]
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.8.5</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>3.0.3.RELEASE</version>
            <type>jar</type>
            <scope>compile</scope>
        </dependency>
[/xml]

The test class will run with SpringJUnit4ClassRunner:

[java]
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/spring-context-test.xml" })
public class CachingTest {

	@Autowired
	ApplicationContext context;

	@Autowired
	CacheManager cacheManager;

	MessageStorage storage;

	MessageStorage storageDelegate;

	MessageController controller;

	@Before
	public void before() throws Exception {
		storageDelegate = Mockito.mock(MessageStorage.class);
		storage = (MessageStorage) context.getBean("messageStorage");
		storage.setDelegate(storageDelegate);
		controller = new MessageController(storage);

		cacheManager.clearAll();
	}

	@Test
	public void testCaching_MessagesCache() {
		controller.getAllMessages();
		controller.getAllMessages();
		verify(storageDelegate, times(1)).findAllMessages();
	}

	@Test
	public void testCaching_MessagesCacheRemove() {
		controller.getAllMessages();
		controller.getAllMessages();
		controller.addMessage(new Message());
		controller.getAllMessages();

		verify(storageDelegate, times(2)).findAllMessages();
		verify(storageDelegate, times(1)).addMessage(any(Message.class));
	}

	@Test
	public void testCaching_MessageCache() {
		controller.getMessageById(1);
		controller.getMessageById(1);
		controller.addMessage(new Message());
		controller.getMessageById(1);

		verify(storageDelegate, times(1)).findMessage(1);
		verify(storageDelegate, times(1)).addMessage(any(Message.class));
	}
}
[/java]

The delegate is a mock object that is used for checking the real number of invocations on a MessageStorage. The Spring context for test looks like this:

[xml]
<?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:context="http://www.springframework.org/schema/context" xmlns:ehcache="http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring http://ehcache-spring-annotations.googlecode.com/svn/schema/ehcache-spring/ehcache-spring-1.1.xsd">

    <ehcache:annotation-driven />

    <ehcache:config cache-manager="cacheManager">
        <ehcache:evict-expired-elements interval="60" />
    </ehcache:config>

    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation" value="classpath:ehcache-test.xml" />
    </bean>

    <bean id="messageStorage" class="com.goyello.esa.storage.MemoryMessageStorage" />

</beans>
[/xml]

Now we are ready to run the created test to verify the actual invocations on MemoryMessageStorage. Results we wish to see: test

The summary

Using Ehcache Spring Annotations is straightforward. With a couple of simple steps you may introduce caching in your application. I strongly suggest that before using the library in live project you visit the project website and read the documentation to be aware of other functionalities not covered in this article.

Update: Spring 3.1 release, among many enhancements, brings native support for method caching with so-called cache abstraction. To read more about this feature check Quick start with method caching using Spring 3.1 and Ehcache follow-up post.

References

Ehcache Spring Annotations project page – http://code.google.com/p/ehcache-spring-annotations/ Ehcache project page – http://ehcache.org/ Spring 3.0 reference – http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/ Mockito – http://mockito.org/ Maven2 – http://maven.apache.org/

Download and run the project

The complete project can be downloaded here: demo-project After you download the file, unpack it, navigate to the project folder and run following command (maven2 needs to be installed): mvn clean tomcat:run Then navigate to you browser to see the working application:

Tags:

Aspire Blog Team

Aspire Systems is a global technology services firm serving as a trusted technology partner for our customers. We work with some of the world's most innovative enterprises and independent software vendors, helping them leverage technology and outsourcing in our specific areas of expertise. Our services include Product Engineering, Enterprise Solutions, Independent Testing Services and IT Infrastructure Support services. Our core philosophy of "Attention. Always." communicates our belief in lavishing care and attention on our customers and employees.

26 comments

  1. I followed all the steps mentioned above but it doesn’t seem to work. I do not see any ‘Initialised cache: ‘ message. My log4j level is set to INFO.
    Don’t I need to set ‘hibernate.cache.use_second_level_cache’ to true in application-context.xml and set values for ‘hibernate.cache.provider_class’?

    1. No, you don’t need hibernate cache provider, because this is not hibernate cache. Checkout demo project attached to the post, you will find all the configuration there.

      1. I’ve used this project in tomcat without any error but when moving it to
        virgo web server got some error that is web application failed to
        start, how to resolve this plz help me 

        1. I build projects usually on Tomcat server. Unfortunately, I don’t know Virgo and I never used it. So I think I cannot help you here. I am really sorry.

  2.  Great Article. Explained in very clear fashion. It worked the first time like Magic. Thank you very much.

  3. When selfPopulating=”true” for @Cacheable and ehcache:annotation-driven settings have self-populating-cache-scope=”method” It creates different instances for each annotated method. How do I use @TriggersRemove to remove the item from its respective cache instance

  4. I have downloaded above project and executed as it is ,

    It seems need to modify the config,

    First calll to – http://localhost:8080/esa/message.html,
    it shows below output

    DEBUG [com.googlecode.ehcache.annotations.interceptor.EhCacheInterceptor]: Generated key ‘183410223502399’ for invocation: ReflectiveMethodInvocation:
     public abstract com.goyello.esa.model.Message com.goyello.esa.storage.MessageStorage.findMessage(long); target is of class [com.goyello.esa.storage.M
    emoryMessageStorage]

    Then click id – 1 to view message – It shows below message
    DEBUG [com.goyello.esa.storage.MemoryMessageStorage]: == find message by id=1
    DEBUG [com.googlecode.ehcache.annotations.interceptor.EhCacheInterceptor]: Generated key ‘167334053963’ for invocation: ReflectiveMethodInvocation: pu
    blic abstract java.util.Collection com.goyello.esa.storage.MessageStorage.findAllMessages(); target is of class [com.goyello.esa.storage.MemoryMessage
    Storage]

    Click viewALL to see list of message – below o/p is shown
    DEBUG [com.googlecode.ehcache.annotations.interceptor.EhCacheInterceptor]: Generated key ‘183410223502399’ for invocation: ReflectiveMethodInvocation:
     public abstract com.goyello.esa.model.Message com.goyello.esa.storage.MessageStorage.findMessage(long); target is of class [com.goyello.esa.storage.M
    emoryMessageStorage]

    BUT again calling to id – 1 why below o/p is shown, data should retrieve from cache ??
    so it means it is not caching data..
    DEBUG [com.googlecode.ehcache.annotations.interceptor.EhCacheInterceptor]: Generated key ‘167334053963’ for invocation: ReflectiveMethodInvocation: pu
    blic abstract java.util.Collection com.goyello.esa.storage.MessageStorage.findAllMessages(); target is of class [com.goyello.esa.storage.MemoryMessage
    Storage]

  5. Very nice article/tutorial – u click once and it’s running … unlike so many others…n/c

  6. If you want to use gradle instead of maven, just put the following into a build.gradle file and import:
    —————————————————————–
    apply plugin: ‘java’
    apply plugin: ‘eclipse’

    repositories {
    mavenCentral()
    }

    dependencies {

    compile ‘org.springframework:spring-webmvc:3.2.3.RELEASE’
    compile ‘org.springframework:spring-web:3.2.3.RELEASE’
    compile ‘org.springframework:spring-context:3.2.3.RELEASE’
    compile ‘org.springframework:spring-context-support:3.2.3.RELEASE’
    compile ‘com.googlecode.ehcache-spring-annotations:ehcache-spring-annotations:1.2.0’
    compile ‘org.slf4j:slf4j-log4j12:1.6.1’
    testCompile ‘org.mockito:mockito-all:1.9.5’
    testCompile ‘junit:junit:4.11’
    testCompile ‘org.springframework:spring-test:3.2.3.RELEASE’
    }

Comments are closed.