In this post, we will explore how to create a custom key generator with Spring Cache. Read our article cache Spring Caching the Spring cache introduction.
Introduction
Since caches are essentially key-value stores, each invocation of a cached method needs translation into a suitable key for cache access. In this post, we are covering the default key generation features provided by Spring Cache API. We are also going to cover the option to create a custom key generator with Spring Cache.
1. KeyGenerator
Spring Cache API uses a simple KeyGenerator
for generating a key to store caching data. The default key generators for Spring Cache SimpleKeyGenerator.This default implementation uses the method parameters to generate the key. Here is the high-level overview for the default key generation algorithm.
- If no params are given, return
SimpleKey.EMPTY
. - With only one parameter, return that instance.
- If more the one param is given, return a
SimpleKey
containing all parameters.
Above approach works for most of the use cases, however, there are certain use cases where the above algorithm can cause collision while creating the key.
- In the case of two methods with the same parameters and cache name.
Let’s look at an example to understand this more clearly.
@CacheConfig(cacheNames = "customer")
public class CustomerService {
@Cacheable
public Customer getCustomer(Integer customerId) {
return // ...
}
@Cacheable
public EliteCustomer getEliteCustomer(Integer id) {
return // ...
}
}
[pullquote align=”normal”]The default key generation strategy changed with the release of Spring 4.0. Earlier versions of Spring used a key generation strategy that, for multiple key parameters, only considered the hashCode()
of parameters and not equals();
This could cause unexpected key collisions. The new ‘SimpleKeyGenerator’ uses a compound key for such scenarios. [/pullquote]
2. Custom KeyGenerator
Spring Caching API provides options to create a custom key generator for handling all such use cases. To give a different default key generator, we need to implement the org.springframework.cache.interceptor.KeyGenerator
interface.KeyGenerator
needs to implement a single method.
/**
* Generate a key for the given method and its parameters.
* @param target the target instance
* @param method the method being called
* @param params the method parameters (with any var-args expanded)
* @return a generated key
*/
Object generate(Object target, Method method, Object...params);
Let’s take a look at the custom key generator for Spring Caching
public class CustomKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object...params) {
return target.getClass().getSimpleName() + "_" + method.getName() + "_" +
StringUtils.arrayToDelimitedString(params, "_");
}
}
For more complex use cases, we can create a CustomKey class and implement both equals()
and hashCode().
public class CustomCacheKey implements Serializable {
public static final CustomCacheKey EMPTY = new CustomCacheKey();
private final Object[] params;
private final int hashCode;
public CustomCacheKey(Object...elements) {
Assert.notNull(elements, "null value");
this.params = new Object[elements.length];
System.arraycopy(elements, 0, this.params, 0, elements.length);
this.hashCode = Arrays.deepHashCode(this.params);
}
@Override
public boolean equals(Object obj) {
return (this == obj || (obj instanceof CustomCacheKey &&
Arrays.deepEquals(this.params, ((CustomCacheKey) obj).params)));
}
@Override
public final int hashCode() {
return this.hashCode;
}
@Override
public String toString() {
return getClass().getSimpleName() + " [" + StringUtils.arrayToCommaDelimitedString(this.params) + "]";
}
}
This is how our custom key generator looks like
public class CustomKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object...params) {
return generateKey(params);
}
/**
* Generate a key based on the specified parameters.
*/
public static Object generateKey(Object...params) {
if (params.length == 0) {
return CustomCacheKey.EMPTY;
}
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
return new CustomCacheKey(params);
}
}
3. Using Custom KeyGenerator
We have our custom key generator, to use it, we have the following two options to use the custom key in our Spring Cache application.
3.1 Use CachingConfigurerSupport
The first option is to implement CachingConfigurer.
Recommendation is to extend from CachingConfigurerSupport.
@EnableCaching
@Configuration
public class ApplicationConfig extends CachingConfigurerSupport {
@Bean("customKeyGenerator")
public KeyGenerator keyGenerator() {
return new CustomKeyGenerator();
}
}
Extending from CachingConfigurerSupport helps in registering the declared KeyGenerator with the interceptors.
3.2 Method Level KeyGenerator
If you like to use, a custom key generator for certain methods, pass it as keyGenerator attribute in the @Cacheable
annotation.
@Component
public class DefaultProductService implements ProductService {
@Override
@Cacheable("products", keyGenerator = "customKeyGenerator")
public List < Product > getProducts() {
List < Product > productList = new ArrayList < > ();
for (int i = 0; i < 9; i++) {
simulateSlowness();
productList.add(new Product(String.valueOf(i), "Demo Product", "Sample Description"));
}
return productList;
}
}
3.3 SPEL Expressions
The @Cacheable
annotation allows the user to specify key generation algorithm through its key
attribute. We can use SpEL to pick the arguments for key generation. Here are some of the examples.
@Cacheable(cacheNames="address", key="#customer")
public Address getAddress(final Customer customer)() {...}
@Cacheable(cacheNames="address", key="#customer.id")
public Address getAddress(final Customer customer)() {...}
[pullquote align=”normal”]The key and keyGenerator parameters are mutually exclusive and an operation specifying both will result in an exception. [/pullquote]
Summary
In this article, we explore how to create a custom key generator with Spring Cache. We discuss the features and capabilities of the default key generator. We also discuss the steps of implementing a custom Spring Cache’s KeyGenerator.
You can download the source code for this post from GitHub