Sunday, 21 July 2013

Caching with Spring Cache

From time to time we need to use a cache in our application.
Instead of writing it on our own and reinventing the wheel, we can use Spring Cache.
Its biggest advantage is unobtrusiveness - besides few annotations we keep our code intact.

Let's see how to do it.
At first let's have a look at some fake service that is the reason for using cache (in real life it may be some database call, web service etc.)

public class SlowService {
private Logger logger = LoggerFactory.getLogger(SlowService.class.getName());
public boolean isVipClient(String clientName) {
logger.debug("Checking " + clientName);
boolean result = false;
if (clientName.hashCode() % 2 == 0) {
result = true;
}
return result;
}
}

We will cache the invocations of isVipClient(...) 

Let's start with adding dependencies to our project:

<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.2.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.2.3.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.kubek2k</groupId>
<artifactId>springockito</artifactId>
<version>1.0.5</version>
<scope>test</scope>
</dependency>
view raw dep.xml hosted with ❤ by GitHub

We'd like to use xml-free spring config, so we will define our beans in java:

@Configuration
@EnableCaching
@ComponentScan("pl.mjedynak")
public class AppConfig {
public static final String VIP_CLIENTS_CACHE = "vipClients";
@Bean
public SlowService slowService() {
return new SlowService();
}
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache(VIP_CLIENTS_CACHE)));
return cacheManager;
}
}
view raw AppConfig.java hosted with ❤ by GitHub

We have our SlowService defined as a bean. We also have the cacheManager along with a cache name and its implementation - in our case it's based on ConcurrentHashMap. We can't forget about enabling caching via annotation.

The only thing to do is to add annotation @Cacheable with a cache name to our method:

@Cacheable(AppConfig.VIP_CLIENTS_CACHE)
public boolean isVipClient(String clientName) { ...
view raw exc.java hosted with ❤ by GitHub


How to test our cache?
One way to do it is to check if the double invocation of our method with the same parameter will actually execute the method only once.
We will use springockito to inject a spy into our class and verify its behaviour.
We need to start with a spring test context:

<?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:mockito="http://www.mockito.org/spring/mockito"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.mockito.org/spring/mockito classpath:spring/mockito.xsd">
<context:component-scan base-package="pl.mjedynak"/>
<mockito:spy beanName="slowService"/>
</beans>

Basically the context will read the configuration from java class and will replace the slowService bean with a spy. It would be nicer to do it with an annotation but at the time of this writing the @WrapWithSpy doesn't work (https://bitbucket.org/kubek2k/springockito/issue/33).

Here's our test:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class SlowServiceIntegrationTest {
@Autowired
private SlowService slowService;
@Test
public void shouldUseCacheWhenCallingCachedMethodWithTheSameParameter() {
// given
String clientName = "WakaWaka";
// when
slowService.isVipClient(clientName);
slowService.isVipClient(clientName);
// then
verify(slowService).isVipClient(clientName);
}
}

This was the very basic example, spring cache offers more advanced features like cache eviction and update.

The whole project can be found at github.