The Java Cache API allows you to add a @CacheResult
annotation to a method, which means that calls to the method will be cached:
import javax.cache.annotation.CacheResult; @CacheResult public String exampleMethod(String a, int b) { // ... }
The cache will be looked up before the annotated method executes. If a value is found in the cache it is returned and the annotated method is never actually executed.
The cache lookup is based on the method parameters. By default a cache key is generated by a key generator that uses Arrays.deepHashCode(Object[])
and Arrays.deepEquals(Object[], Object[])
on the method parameters. The cache lookup based on this key is similar to a HashMap lookup.
You can define and configure multiple caches in your application and reference them by name via the cacheName
parameter of the @CacheResult
annotation:
@CacheResult(cacheName="examplecache") public String exampleMethod(String a, int b) {
If no cache name is given the cache name is based on the fully qualified method name and the types of its parameters, for example in this case: “my.app.Example.exampleMethod(java.lang.String,int)”. This way there will be no conflicts with other cached methods with the same set of parameters.
Custom Key Generators
But what if you actually want to use the same cache for multiple methods without conflicts? The solution is to define and use a custom cache key generator. In the following example both methods use the same cache (“examplecache”), but also use a custom cache key generator (MethodSpecificKeyGenerator
):
@CacheResult( cacheName="examplecache", cacheKeyGenerator=MethodSpecificKeyGenerator.class) public String exampleMethodA(String a, int b) { // ... } @CacheResult( cacheName="examplecache", cacheKeyGenerator=MethodSpecificKeyGenerator.class) public String exampleMethodB(String a, int b) { // ... }
Now we have to implement the MethodSpecificKeyGenerator
:
import org.infinispan.jcache.annotation.DefaultCacheKey; import javax.cache.annotation.CacheInvocationParameter; import javax.cache.annotation.CacheKeyGenerator; import javax.cache.annotation.CacheKeyInvocationContext; import javax.cache.annotation.GeneratedCacheKey; public class MethodSpecificKeyGenerator implements CacheKeyGenerator { @Override public GeneratedCacheKey generateCacheKey(CacheKeyInvocationContext<? extends Annotation> context) { Stream<Object> methodIdentity = Stream.of(context.getMethod()); Stream<Object> parameterValues = Arrays.stream(context.getKeyParameters()).map(CacheInvocationParameter::getValue); return new DefaultCacheKey(Stream.concat(methodIdentity, parameterValues).toArray()); } }
This key generator not only uses the parameter values of the method call but also the identity of the method to generate the key. The call to context.getMethod()
returns a java.lang.reflect.Method
instance for the called method, which has appropriate hashCode()
and equals()
implementations. Both this method object and the parameter values are passed to the DefaultCacheKey implementation, which uses deep equality on its parameters, as mentioned above.
By adding the method’s identity to the cache key we have ensured that there will be no conflicts with other methods when using the same cache.
Hi,
Interesting article, I was not aware of this cache functionality in Java, but it might come in handy.
However, I have one question, why would someone use the same cache for different methods? Is there any benefit from using one cache instead of the automatically generated individual caches per method?
Thanks & Best Regards,
Tobias