Caching Made Easy with Spring

5 months ago by in Articles, Spring Tagged: , , , ,

Spring 3.1 introduced a new and simply way to cache results. In this article we will see how we can use the new Spring caching within our projects in order to avoid repeating task for which outcome was already produced. The readers of this article are expected to have some basic knowledge about Spring and dependency injection (or as it also referred to: inversion of control).

All code listed below is available at: http://code.google.com/p/java-creed-examples/source/checkout. Most of the examples will not contain the whole code and may omit fragments which are not relevant to the example being discussed. The readers can download or view all code from the above link.

This article is divided three sections. In the first section we see a simple example, just enough to get us going. In the second section we look under the hood and see a how the caching works with respect to recursion. In the last section we will see a simple, but real-world like, example where caching is applied. In the last section, we will also see how to clear the cache when this is expired or invalid.

Caching with Spring

Consider the following class.

package com.javacreed.examples.sc.part1;

import org.springframework.stereotype.Component;

@Component
public class Worker {

  public String longTask(final long id) {
    System.out.printf("Running long task for id: %d...%n", id);
    return "Long task for id " + id + " is done";
  }

  public String shortTask(final long id) {
    System.out.printf("Running short task for id: %d...%n", id);
    return "Short task for id " + id + " is done";
  }
}

Here we have a simple Spring component class that has two methods. One method, named longTask(), carries a fictitious long task, while the second method, named shortTask(), runs quickly. The output of both methods is only determined by the input of these methods. Therefore, for the same input we will always get the same output. This is very important, as otherwise we cannot apply caching.

Now consider the following class.

package com.javacreed.examples.sc.part1;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

  public static void main(final String[] args) {
    final String xmlFile = "META-INF/spring/app-context.xml";
    try (ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(xmlFile)) {

      final Worker worker = context.getBean(Worker.class);
      worker.longTask(1);
      worker.longTask(1);
      worker.longTask(1);
      worker.longTask(2);
      worker.longTask(2);
    }
  }
}

Here we are creating the Spring environment and retrieving an instance of our Worker from Spring. Then we invoke the method longTask() five times. This will produce the following output to the command prompt.

Running long task for id: 1...
Running long task for id: 1...
Running long task for id: 1...
Running long task for id: 2...
Running long task for id: 2...

Note that the method longTask() runs five times, one for every request. Also note that this method only received two different inputs. This method is invoked three times with the parameter value: 1 and twice with the parameter value: 2. Since the output of this fictitious long task method is only determined by its input, we can cache the output and on the next request for this input we use the cached value instead of re-running this fictitious long task.

In order to apply caching we need to do the following three things.

  1. Mark the methods (or classes) which output will be cached.
    Spring 3.1 added new annotations that enable method caching as shown next.

    package com.javacreed.examples.sc.part1;
    
    import org.springframework.cache.annotation.Cacheable;
    import org.springframework.stereotype.Component;
    
    @Component
    public class Worker {
    
      @Cacheable("task")
      public String longTask(final long id) {
        System.out.printf("Running long task for id: %d...%n", id);
        return "Long task for id " + id + " is done";
      }
    
      public String shortTask(final long id) {
        System.out.printf("Running short task for id: %d...%n", id);
        return "Short task for id " + id + " is done";
      }
    
    }
    

    By simply adding the @Cacheable annotation (Java Doc) to the method signature, repetitive requests to this method, with the same parameter value, will simply return the cached value. Spring allows us to cache values without having to write the boilerplate-code that handles that. Note that this annotation also takes a value, which is the name of the cache repository. We will talk about the cache repository in a minute.

    Observation

    Note that the annotation @Cacheable can be applied to a class, which means that all methods of that class are cached.
  2. Enabled Spring caching.
    Before Spring can start caching our values, we need add the following declaration.

    <?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: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-3.1.xsd
        http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
    
      <context:annotation-config />
      <context:component-scan base-package="com.javacreed.examples.sc" />
    
      <!-- Enables the caching through annotations -->
      <cache:annotation-driven />
    
    </beans>
    

    With this declaration, Spring will look for any classes or methods that are marked cacheable and will take all necessary actions in order to provide caching.

  3. Configure the caching repository to be used.
    Before this code can work, we need to define the cache repository as shown next.

    <?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: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-3.1.xsd
        http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
    
      <context:annotation-config />
      <context:component-scan base-package="com.javacreed.examples.sc" />
    
      <!-- Enables the caching through annotations -->
      <cache:annotation-driven />
    
      <!-- Generic cache manager based on the JDK ConcurrentMap -->
      <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
          <set>
            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="task" />
          </set>
        </property>
      </bean>
    </beans>
    

    The cache repository is where the actual objects are saved. Spring supports two types of repositories: one based on JDK ConcurrentMap and the other on ehcache popular library. More can be added. Here we are using the JDK ConcurrentMap as our cache repository. The repository plays little (if any) effect on the code and switching between repositories should be very easy. Our objects will be cached within a ConcurrentMap. In this example we added one cache repository, named task. We can have more than one repository. Note that the name of this repository is the same as the name showed in the annotation before.

    Observation

    Please note that the JDK ConcurrentMap class shown in the above declaration varies between versions of Spring 3.1 and 3.2. Here we are using Spring 3.2. In version 3.1 the class name is as shown below.

    org.springframework.cache.concurrent.ConcurrentCacheFactoryBean
    

How does this work?

Spring, when configured to use caching, will wrap the objects that are marked to be cached in to a proxy. The caller will not work with our object but with the proxy instead as shown in the following figure.

Caching Proxy

Caching Proxy

If we had to print the class’s canonical name of the object returned by the Spring environment, we will see the following.

Worker class: com.javacreed.examples.sc.part1.Worker$$EnhancerByCGLIB$$4fa6f80b

Note that this is not the Worker class (canonical name: com.javacreed.examples.sc.part1.Worker) we created but some other class. In fact, this class was generated by Spring using code generation techniques, which are not discussed here. When we invoke any methods from the Worker class, instead we are calling the methods in the generated proxy. This proxy holds an instance of our Worker class. It will forward any request to our object and return its response as shown in the following figure. If the method is marked as cacheable, then the proxy will bypass the request and will return the cached value instead. If the proxy does not have a cached value yet for the given input, it makes the request and saves the response for future use.

Proxy Flow of Control

Proxy Flow of Control

If we run again our Main class, we will get the following output.

Running long task for id: 1...
Running long task for id: 2...

Here the fictitious long task method (longTask()) is actually invoked twice. The proxy returned the cached result for the other times. This concludes our first section about Spring caching. As we saw, it is quite easy to enable. All we need to do is follow the three steps listed before, and we have caching. In the next section we will see how we can apply caching with recursion.

Caching a recursive method

Consider the following class.

package com.javacreed.examples.sc.part2_1;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;

@Component("fibonacci")
public class Fibonacci {

  private int executions = 0;

  public int getExecutions() {
    return executions;
  }

  public void resetExecutions() {
    this.executions = 0;
  }

  @Cacheable("fibonacci")
  public long valueAt(final long index) {
    executions++;
    if (index < 2) {
      return 1;
    }

    return valueAt(index - 1) + valueAt(index - 2);
  }

}

This class implements Fibonacci sequence and returns the Fibonacci number at a given index. The Fibonacci number is calculated recursively using the function: fib(n) = fib(n-1) + fib(n-2). The base case for this recursive function is that the first two Fibonacci numbers are 1.

Note that this class also keeps track of how many times the valueAt() method is invoked. We can obtain this value through the getter method. The Fibonacci class also enabled resetting of this value so that the counter starts from 0 once more.

Here everything seems to be in place and the class is enabled for caching. So let us execute it.

package com.javacreed.examples.sc.part2_1;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

  public static void main(final String[] args) {
    final String xmlFile = "META-INF/spring/app-context.xml";
    try (ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(xmlFile)) {

      final long start = System.nanoTime();
      final Fibonacci sequence = context.getBean("fibonacci", Fibonacci.class);
      final long fibNumber = sequence.valueAt(5);
      final int executions = sequence.getExecutions();
      final long timeTaken = System.nanoTime() - start;
      System.out.printf("The 5th Fibonacci number is: %d (%,d executions in %,d NS)%n", fibNumber, executions,
          timeTaken);
    }
  }
}

If we run the above code, we will get the following output.

The 5th Fibonacci number is: 8 (15 executions in 17,762,022 NS)

The cacheable method valueAt() was invoked a total of 15 times. This does not sound right. The valueAt() method should have been executed only 6 times and not 15 times. The other times 9 times, the cached value should have been returned instead.

What went wrong?
In the main() method we obtained an instance of the Fibonacci class through Spring. In turn, Spring wrapped our object into a proxy. Therefore within the main() method, we only have access to the proxy. But the valueAt() method within the Fibonacci class, calls itself (recursion). This is not calling the valueAt() method through the proxy, but directly from Fibonacci class. Therefore the proxy is bypassed. That is why we are not caching the value at the recursion level.

Observation

Note that if we had to invoke the sequence.valueAt(5); (with the same value) again, the cached value will be returned as the variable sequence is an instance of the proxied Fibonacci.

How can we fix this?
In order to fix this we need to modify the Fibonacci class and pass a reference of our proxy as shown next.

package com.javacreed.examples.sc.part2_2;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;

@Component("fibonacci2")
public class Fibonacci {

  private int executions = 0;

  public int getExecutions() {
    return executions;
  }

  public void resetExecutions() {
    this.executions = 0;
  }

  @Cacheable("fibonacci")
  public long valueAt(final long index, final Fibonacci callback) {
    executions++;
    if (index < 2) {
      return 1;
    }

    return callback.valueAt(index - 1, callback) + callback.valueAt(index - 2, callback);
  }

}

Note that now our valueAt() method takes two parameters and not one. It takes an instance of the Fibonacci class, referred to as callback. Furthermore, instead of invoking the valueAt() on itself, it invokes the callback‘s valueAt(). From the main() method, we need to also pass an instance of the proxied Fibonacci class as shown in the following example.

package com.javacreed.examples.sc.part2_2;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

  public static void main(final String[] args) {
    final String xmlFile = "META-INF/spring/app-context.xml";
    try (ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(xmlFile)) {

      final long start = System.nanoTime();
      final Fibonacci sequence = context.getBean("fibonacci2", Fibonacci.class);
      final long fibNumber = sequence.valueAt(5, sequence);
      final int executions = sequence.getExecutions();
      final long timeTaken = System.nanoTime() - start;
      System.out.printf("The 5th Fibonacci number is: %d (%,d executions in %,d NS)%n", fibNumber, executions,
          timeTaken);
    }
  }
}

This class will produce the following output.

The 5th Fibonacci number is: 8 (6 executions in 18,320,003 NS)

Observation

Note that this example, took longer than the non-cached version. This is because caching adds some overheads, and this example did not benefit enough to overcome these costs. If instead we try to retrieve a larger Fibonacci number, such as 50, then the benefit will appear. Please do not try very large numbers as these will take a considerable amount of time to compute.

This concludes our example with caching and using recursion. The key point is that recursion will bypass the proxy unless a callback is specified. In the following section we will see a simple, real world like, example.

Real-world like example

Consider the following domain class.

package com.javacreed.examples.sc.part3;

public class Member {

  private final int memberId;
  private final String memberName;

  public Member(final int memberId, final String memberName) {
    this.memberId = memberId;
    this.memberName = memberName;
  }

  // Getters removed for brevity

  @Override
  public String toString() {
    return String.format("[%d] %s", memberId, memberName);
  }
}

This is a simple class that represents a member having only an id, which uniquely identifies a member and a name. For simplicity, the members are persisted in a text file of the format shown next.

1,Albert Attard
2,Mary Borg
3,Tony White
4,Jane Black

Now consider the following service interface.

package com.javacreed.examples.sc.part3;

public interface MembersService {

  Member getMemberWithId(int id);

  void saveMember(Member member);
}

This interface exposed two methods one used to retrieve the member with the given id and the other to persist any modifications made to file. The following class starts the whole thing and makes several request to the MembersService implementation. Note that the implementation is cacheable and therefore we are working with the proxy version of the MembersService.

package com.javacreed.examples.sc.part3;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

  public static void main(final String[] args) {
    final String xmlFile = "META-INF/spring/app-context.xml";
    try (ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(xmlFile)) {

      final MembersService service = context.getBean(MembersService.class);

      // Load member with id 1
      Member member = service.getMemberWithId(1);
      System.out.println(member);

      // Load member with id 1 again
      member = service.getMemberWithId(1);
      System.out.println(member);

      // Edit member with id 1
      member = new Member(1, "Joe Vella");
      service.saveMember(member);

      // Load member with id 1 after it was modified
      member = service.getMemberWithId(1);
      System.out.println(member);
    }
  }
}

If we execute the above, the following is produced to the command prompt.

Retrieving the member with id: [1] from file: C:\javacreed\spring-cache\members.txt
[1] Albert Attard
[1] Albert Attard
Retrieving the member with id: [1] from file: C:\javacreed\spring-cache\members.txt
[1] Joe Vella

Here we made two requests to retrieve the member with id 1, but the method was actually invoked once. In the second request, the cached value was returned. Then we modified the member with the same id. Since the member was modified, the cache was invalidated (we will see how shortly). Thus when retrieving the member with the same id, we invoked the actual method again and load it from file. This value will be cached until invalidated again.

Now let us see how this is implemented. The getMemberWithId() is similar to the other methods we saw already. It is annotated with the @Cacheable annotation.

  @Override
  @Cacheable("members")
  public Member getMemberWithId(final int id) {
    System.out.printf("Retrieving the member with id: [%d] from file: %s%n", id, dataFile.getAbsolutePath());
   // code removed for brevity
  }

The saveMember() needs to invalidate the cache. In order to achieve this, Spring provides another annotation named: @CacheEvict (Java Doc) as shown next.

  @Override
  @CacheEvict(value = "members", allEntries = true)
  public void saveMember(final Member member) {
   // code removed for brevity
  }

Whenever this method is invoked, the cache repository named members is cleared from all members (as instructed by the: allEntries = true annotation optional parameter). Therefore, next time the getMemberWithId() method is invoked it will have to load the member from file, thus reading the new changes. Without this, the getMemberWithId() method will still return the old version of the member with id 1.

This concludes our article about Caching with Spring. In this article we saw how we can leverage existing API in order to cache results without having to write any boilerplate code.

The author didnt add any Information to his profile yet

Leave a Comment



+ two = 11