Sunday, June 22, 2014

Simple in-memory caching with Spring 3.2 and Google Guava

    There was a proposal (not really original :)) that we can use cache to relieve external system and make our own system faster. It should be a really simple in-memory cache without any replication and fancy mechanisms. It should only have max-number-of-elements and expiration-time-after-write properties. Can that requirement be implemented in Spring easily?
My first shot was to use org.springframework.cache.support.SimpleCacheManager with org.springframework.cache.concurrent.ConcurrentMapCache. However, it did not provide max-number-of-elements and expiration-time-after-write functionalities. Then I tried to switch to Ehcache. It could do the trick but it seemed to be too heavyweight for me (I also noticed two additional threads which were spanned by Ehcache). The ideal candidate was the cache provided by Guava library - com.google.common.cache.LocalCache. On the other hand, Spring 3.2 does not provide abstraction implementation for Guava cache. After some Googling, it turned out that the implementation is available starting from Spring 4 (GuavaCacheManager and GuavaCache). I did not want to update Spring to the new version (at least not yet :)). I decided to incorporate these two classes with minor modification of GuavaCache class - two new methods were added to org.springframework.cache.Cache interface in Spring 4 and I simply removed them from GuavaCache and then everything was ready to go:
@Configuration
@ComponentScan("test")
@EnableCaching
public class Config {

 @Bean
 public CacheManager cacheManager() {
  GuavaCacheManager cacheManager = new GuavaCacheManager();
  cacheManager.setCacheBuilder(CacheBuilder.newBuilder()
  .expireAfterWrite(2, TimeUnit.SECONDS).maximumSize(100));
  return cacheManager;
 }
}

@Service
public class ComputationService {
 @Cacheable("computation-cache")
 public String compute(String data, int number) {
  System.out.println("Computation");
  return "Computed";
 }
}

public class Main {

 public static void main(String[] args) throws InterruptedException {

  AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
  ComputationService service = ctx.getBean(ComputationService.class);
  for (int i = 0; i < 10; i++) {
   service.compute("data", 1);
   service.compute("data", 2);
   service.compute("data2", 1);
   service.compute("data2", 2);   
  }
  System.out.println("SLEEP");
  TimeUnit.SECONDS.sleep(5);
  for (int i = 0; i < 10; i++) {
   service.compute("data", 1);
   service.compute("data", 2);
   service.compute("data2", 1);
   service.compute("data2", 2);   
  }
 }
}

and the result was:
Computation
Computation
Computation
Computation
SLEEP
Computation
Computation
Computation
Computation
Worked like a charm!

4 comments :