Spring 3.1 Cache Abstraction Tutorial
Spring Framework provides support for transparently adding caching into an existing Spring application. Similar to the transaction support, the caching abstraction allows consistent use of various caching solutions with minimal impact on the code.
At its core, the abstraction applies caching to Java methods, reducing thus the number of executions based on the information available in the cache. That is, each time a targeted method is invoked, the abstraction will apply a caching behaviour checking whether the method has been already executed for the given arguments. If it has, then the cached result is returned without having to execute the actual method; if it has not, then method is executed, the result cached and returned to the user so that, the next time the method is invoked, the cached result is returned.
This concept is of course not something new. You can check out Spring, AspectJ, Ehcache Method Caching Aspect a very interesting post from Brian Du Preez, one of our JCG partners, in which Aspect Oriented Programming is used.
As its name implies, Cache Abstraction is not an actual implementation, so it requires the use of an actual storage to store the cache data. As you might have guessed, Ehcache support is provided out of the box. There is also an implementation based on JDK’s ConcurrentMap and you can actually plug-in different back-end caches.
Now, let’s see some sample code on caching abstraction. For this purpose, I will use the very informative Cache Abstraction in Spring 3.1.0.M1 post by James Carr, another of our JCG partners. Make sure to bookmark the Spring Cache package Javadocs along the way.
(NOTE: The original post has been slightly edited to improve readability)
Another new feature released yesterday came in parallel with me trying out some annotation based caching strategies. Caching Abstraction basically takes convention from an existing project and makes it part of Spring core.
Essentially it introduces a new interface, CacheManager, which can be implemented by a specific cache implementation. From there it adds a few new annotations to make methods cacheable. Here’s an example using my previous posts objects.
package com.jamescarr.example; import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.PostConstruct; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Repository; @Repository public class MemoryMessageRepository implements MessageRepository { private static final Logger LOG = LoggerFactory.getLogger(MemoryMessageRepository.class); private final Map<String, Message> messages = new ConcurrentHashMap<String, Message>(); @Cacheable("message") public Message getMessage(String title){ LOG.info("Fetching message"); return messages.get(title); } @CacheEvict(value="message", key="message.title") public void save(Message message){ LOG.info("Saving message"); messages.put(message.getTitle(), message); } public Collection<Message> findAll() { return messages.values(); } @PostConstruct public void addSomeDefaultMessages(){ save(new Message("Hello", "Hello World")); save(new Message("Appointment", "Remember the milk!")); } }
Here you’ll notice that the finder method has a @Cachable annotation on it with a name that specifies the cache to store to. It can also use additional attributes, for example a key which uses an expression language to determine a key from the arguments that are passed in. The default is the value of all the method arguments. On the save method I use @CacheEvict to remove the cached element from the cache if it already exists.
This of course won’t work on it’s own, so you’ll have to enable it yourself (which is good… the last thing you need is to discover a production app caching things it shouldn’t be caching). Sadly as of the time of this writing I haven’t discovered how to do this in non-xml, so here is the spring xml file to enable it and use ehcache as the implementation.
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cache="http://www.springframework.org/schema/cache" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"> <cache:annotation-driven /> <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache"/> <bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="classpath:com/jamescarr/example/ehcache.xml"/> </beans>
The ehcache configuration:
<ehcache> <diskStore path="java.io.tmpdir"/> <cache name="message" maxElementsInMemory="100" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"/> </ehcache>
And finally adding this to the AppConfiguration, which includes doing a simple @ImportResource.
package com.jamescarr.configuration; import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.ImportResource; import com.jamescarr.example.MessagePrinter; @Configuration @ComponentScan("com.jamescarr.example") @ImportResource("classpath:com/jamescarr/example/cache-context.xml") public class AppConfig { @Autowired private MessagePrinter messagePrinter; @PostConstruct public void doSomething(){ messagePrinter.printMessage("Hello"); messagePrinter.printMessage("Hello"); } public static void main(String[] args) { new AnnotationConfigApplicationContext(AppConfig.class); } }
When running this example there should be a log message for the first time the method is hit, then it is not seen the second time (since it is being pulled from the cache. This is definitely pretty awesome for implementing Memoization for methods that might just have some CPU intensive computations (but give the exact expected results given a set of of inputs). I’m excited about doing some more work in this area… I’ve done method level caching before (it’s common) but it is awesome to be able to use it without having to DIY.
That’s it guys. A straightforward guide to get you started with Spring’s Cache Abstraction from James Carr. Don’t forget to share!
Related Articles:
If you need simple memoization, Guava also have it:
http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/base/Suppliers.html#memoizeWithExpiration(com.google.common.base.Supplier, long, java.util.concurrent.TimeUnit)
very helpful thank u