Spring – Caching Java methods

Caching has become an increasing concern for developers wanting to increase performance but still utilize the flexibility of Web services in their applications. There is no doubt that Web Services are extremely versatile, but the HTTP protocol combined with the marshalling and unmarshalling of SOAP, assuming you aren’t using a REST web service, is very slow and doesn’t perform very well.

The answer for most of this has come in the form of caching. I have used EHCache, my favorite open source Java caching framework on several projects in the past 3 years and I’ve used it in many different ways to increase the performance and response times of Ajax applications. Using a cache is not for the inexperienced. You can easily go overboard and degrade performance by utizing too much memory. Deciding what to and what not to cache should be a decision by your entire development team and agreed upon before implementation. I do not recommend letting a single developer run wild with a cache in hand. It’s probably just as dangerous as running with scissors.

Spring has many different way to handle integration with caching frameworks, including EHCache. Hibernate also supports many caching implementations and I use those as well. For spring 2.5 and below, there is even a library that specifically gives you the ability to use special tags in your context files to handle caching of data.

In this particular example, we are going to look at how we can use Spring, AOP and EHCache to cache the returned data of a java method, so that you don’t actually have to write the code to manage storing the objects in the cache yourself.

<beans xmlns="http://www.springframework.org/schema/beans&quot;
    xmlns:context=”http://www.springframework.org/schema/context&#8221;
    xmlns:bean=”http://www.springframework.org/schema/beans&#8221;
      xmlns:p=”http://www.springframework.org/schema/p” 
    xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance&#8221;
    xmlns:aop=”http://www.springframework.org/schema/aop&#8221;
     xmlns:ehcache=”http://www.springmodules.org/schema/ehcache&#8221;
    xmlns:security=”http://www.springframework.org/schema/security&#8221;
    xmlns:tx=”http://www.springframework.org/schema/tx&#8221;
    xsi:schemaLocation=”
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
     http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
     http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
     http://www.springframework.org/schema/security  http://www.springframework.org/schema/security/spring-security-3.0.xsd
     http://www.springmodules.org/schema/ehcache http://www.springmodules.org/schema/cache/springmodules-ehcache.xsd”&gt;

 <!–  this only works in Spring 2.5 currently and you need a special third-party jar as well.
 

        <ehcache:caching id="getDistributedCache"
                cacheName=”distributedCache” />

 –>

 
    classpath:ehcache-CacheService.xml
 

 
   
 
 
    org.company.cache.METHOD_CACHE
 

 
   
 

 
   
 
 
   
      .*methodOne
      .*methodTwo
   
 

 There is the configuration and now we need a class to actually do the caching work. If you noticed above you can indicate which methods in a Spring Bean get cached by specifying a regular expression matching pattern. So for example, you could have a service class that returns a User (getCachedUser) and in your configuration .*Cached.* to match any spring bean with any method with Cached in it’s name.

import java.io.Serializable;

import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;

    public class MethodCacheInterceptor implements MethodInterceptor, InitializingBean {
      private static final Log logger = LogFactory.getLog(MethodCacheInterceptor.class);

      private Cache cache;

      /**
       * sets cache name to be used
       */
      public void setCache(Cache cache) {
        this.cache = cache;
      }

      /**
       * Checks if required attributes are provided.
       */
      public void afterPropertiesSet() throws Exception {
        Assert.notNull(cache, “A cache is required. Use setCache(Cache) to provide one.”);
      }

      /**
       * main method
       * caches method result if method is configured for caching
       * method results must be serializable
       */
      public Object invoke(MethodInvocation invocation) throws Throwable {
        String targetName  = invocation.getThis().getClass().getName();
        String methodName  = invocation.getMethod().getName();
        Object[] arguments = invocation.getArguments();
        Object result;

        logger.debug(“looking for method result in cache”);
        String cacheKey = getCacheKey(targetName, methodName, arguments);
        Element element = cache.get(cacheKey);
        if (element == null) {
          //call target/sub-interceptor
          logger.debug(“calling intercepted method”);
          result = invocation.proceed();

          //cache method result
          logger.debug(“caching result”);
          element = new Element(cacheKey, (Serializable) result);
          cache.put(element);
        }
        return element.getValue();
      }

      /**
       * creates cache key: targetName.methodName.argument0.argument1…
       */
      private String getCacheKey(String targetName,
                                 String methodName,
                                 Object[] arguments) {
        StringBuffer sb = new StringBuffer();
        sb.append(targetName)
          .append(“.”).append(methodName);
        if ((arguments != null) && (arguments.length != 0)) {
          for (int i=0; i
            sb.append(“.”)
              .append(arguments[i]);
          }
        }

        return sb.toString();
      }
    }

With this example, you can see how generic you can make you caching. You may also want to tweak the ehcache settings as well. Ehcache is extremely configurable. You have many many options. Notice that above we specified a configuration file of ehcache-CacheService.xml. In this file we’ll take care of adding the caches that we want to use. Ehcache support replication via RMI, JMS and JGroups. This example replicates the cache among nodes in the cluster using RMI. Each node is discovered on it’s own so this file is the same on every server.

       
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance&quot;
         xsi:noNamespaceSchemaLocation=”ehcache.xsd” >

       

    
   

   
    <cacheManagerPeerProviderFactory
                        class=”net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory”
                        properties=”hostName=127.0.0.1,
                                    peerDiscovery=automatic, multicastGroupAddress=230.0.0.1,
                                    multicastGroupPort=4446, =32″/>

    <cacheManagerPeerProviderFactory
            class=”net.sf.ehcache.distribution.RMICacheManagerPeerProviderFactory”
            properties=”peerDiscovery=automatic,
                        multicastGroupAddress=230.0.0.1,
                        multicastGroupPort=4446, timeToLive=1″
            propertySeparator=”,”
            />

    

    <cacheManagerPeerListenerFactory
        class=”net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory”
        properties=”port=40001,
                    remoteObjectPort=40002,
                    socketTimeoutMillis=120000″
                    propertySeparator=”,” />

    <cacheManagerPeerListenerFactory
            class=”net.sf.ehcache.distribution.RMICacheManagerPeerListenerFactory”/>

    
    <defaultCache
            maxElementsInMemory=”1000″
            eternal=”true”
            timeToIdleSeconds=”0″
            timeToLiveSeconds=”0″
            overflowToDisk=”true”
            diskSpoolBufferSizeMB=”30″
            maxElementsOnDisk=”0″
            diskPersistent=”false”
            diskExpiryThreadIntervalSeconds=”120″
            memoryStoreEvictionPolicy=”LRU”>
               <cacheEventListenerFactory
                class=”net.sf.ehcache.distribution.RMICacheReplicatorFactory”/>
        <bootstrapCacheLoaderFactory
                class=”net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory”/>
           
            
           
           
           
     <cache name="org.company.cache.METHOD_CACHE"
           maxElementsInMemory=”10000″
           maxElementsOnDisk=”1000″
           eternal=”false”
           overflowToDisk=”true”
           diskSpoolBufferSizeMB=”20″
           timeToIdleSeconds=”300″
           timeToLiveSeconds=”600″
           memoryStoreEvictionPolicy=”LFU”>
             <cacheEventListenerFactory
                class=”net.sf.ehcache.distribution.RMICacheReplicatorFactory”/>
        <bootstrapCacheLoaderFactory
                class=”net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory”/>
           

    
     <cache name="distributedCache"
           maxElementsInMemory=”1000″
           eternal=”true”
           timeToIdleSeconds=”0″
           timeToLiveSeconds=”0″
           overflowToDisk=”true”>
        <cacheEventListenerFactory
                class=”net.sf.ehcache.distribution.RMICacheReplicatorFactory”/>
        <bootstrapCacheLoaderFactory
                class=”net.sf.ehcache.distribution.RMIBootstrapCacheLoaderFactory”/>