@Cachable usage and gotchas

Recently I was working with a colleague to implement caching in an application. Initially it looks fairly straight forward to integrate it, however, because of the way the code was being called we came across a number of problems that I want to share.

To see the code examples of where @Cachable works and where it doesn’t work clone the code from https://github.com/marcthomas2013/ehcache

Dependencies required

This example code is based on Spring Boot 1.2.5, however, when using Spring Boot and ehcache prior to Spring Boot 1.3 you will need to pull in the following dependency to be able to get the ehcacheCacheManager. I struggled to find the dependency that I needed for org.springframework.cache.ehcache.EhCacheCacheManager so, here it is.

<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context-support</artifactId>
   <version>4.2.0.RELEASE</version>
</dependency>

Cachable annotation

Once you have the ehcacheCacheManager set up and an ehcache.xml config file in your resources folder then you are ready to start adding the @Cachable annotation to your public methods.

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true"
         monitoring="autodetect" dynamicConfig="true">

    <!-- <diskStore path="java.io.tmpdir" /> -->
    <diskStore path="~/cache" />

    <cache name="demoCache"
           maxEntriesLocalHeap="10000"
           maxEntriesLocalDisk="1000"
           eternal="false"
           diskSpoolBufferSizeMB="20"
           timeToIdleSeconds="300" timeToLiveSeconds="600"
           memoryStoreEvictionPolicy="LFU"
           transactionalMode="off">
        <persistence strategy="localTempSwap" />
    </cache>
</ehcache>

In the example code we have a cache setup in ehcache.xml with the name “demoCache” this name needs to go into the value parameter. Then the key that you want to use for the cache is set in the “key” field. This key field can be defined as something more than just a parameter name, see Spring’s documentation for more detail.

@Cacheable(value="demoCache", key="#url")
public String getURLResponse(String url) {
    System.out.println("This should only happen once!");
    return "Success";
}

 

Different ways of invoking the cache method

Some ways work and some don’t. In the example code referred to above there is a class MyController that invokes all the cache calls.

In the MyService class the getURLResponse method contains a log line that says “This should only happen once!” this is to show when the method is invoked. If the caching is working then you should only ever see this line once because the cache should be returning the value, however, there are a number of demonstration method calls that show when this doesn’t work and this article explains why.

callServiceDirect explanation

The first call callServiceDirect in MyController is probably the most likely scenario. A controller calling an autowired service method. You’ll see in the log that the target method is called once, and then the response is cached. The second call return value is returned straight from the cache.

callFromObjectWithDependencyPassedIn explanation

The next call callFromObjectWithDependencyPassedIn shows the scenario if you have another class that needs to invoke the service method and you need to pass that dependency in. This approach works and is the way you should do it, but then we’ll compare it with the next method callFromObjectCreatedByTheService that doesn’t work.

callFromObjectCreatedByTheService explanation

There is a method called callFromObjectCreatedByTheService that is very quirky but is used to highlight a Spring Aspect Oriented Programming (AOP) proxy issue that people could get caught out with. When the code is run you will notice that the log shows that the methods @Cachable annotation isn’t invoked. To explain why this is we need to look into the createObjectThatCallsService method in MyService.

What this method is doing is creating another object and passing itself into the object as a form of dependency injection. You might think that this is fine however, it doesn’t work and it is because Spring needs to Proxy the objects that it manages. The Spring Documentation describes this in detail but essentially proxying means that outside of the MyService class references to the object are in fact a Spring Proxy to the MyService object, whereas when you are running code within MyService the object’s this will be the MyService and not the Spring Proxy.

For AOP based annotations like @Cachable to work they need to be invoked using the proxied object and not the directly the object where the method exists. Effectively under the hood when a method with @Cachable is called using a proxied object Spring can then invoke any annotation based calls prior to the actual method being invoked. This is not possible if you are calling MyService direct. This is why this approach doesn’t work.

callingPrivateCacheMethodFromWithinService explanation

callingPrivateCacheMethodFromWithinService is another example of a call to an @Cachable method that doesn’t work and is for the same reason as explained above but for a different use case. The Spring Documentation on caching annotations explains on a tip box on the right with the title “Method visibility and @Cacheable/@CachePut/@CacheEvict” that this won’t work because of a limitation of Spring AOP and it suggests if you want to be able to call private methods internally with AOP based annotations then you will need to setup AspectJ instead. Sadly, I haven’t been able to find a good tutorial on setting up AspectJ instead of Spring AOP.

callingCacheMethodFromWithinService explanation

callingCacheMethodFromWithinService is another example of a call to an @Cachable method that doesn’t work and is for the same reason as explained above but for a different use case. You will notice that this is the is calling the same public method that works for all calls that are external to MyService and as long as they are invoked using the Spring Proxied object they will work, however, this example is calling the @Cachable method inside the MyService which therefore won’t call the method via the proxy object.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s