Spring Cache Abstraction
Spring Cache Abstraction
Spring provides cache abstraction which is a set of interfaces and annotations, used for applying caching to java methods.
Main interfaces are Cache
and CacheManager
.
Spring requires an actual cache implementation in order to store the cached data, e.g. Ehcache.
The caching abstraction has no special handling of multi-threaded and multi-process (i.e. an application deployed on several nodes) environments as such features are handled by cache implementation.
Process
Each time a targeted method is invoked, the cache abstraction will apply a caching behaviour checking whether the method has been already executed for the given arguments. If it has, then the cached result is returned without executing the method. If it has not, then method is executed, the result cached and returned to the user.
Cache Annotations
- @Cacheable
- @CacheEvict
- @CachePut
- @Caching
- @CacheConfig
- @EnableCaching
@Cacheable
As its name implies, it is used to indicate the methods are cacheable.
This annotation declaration requires one or multiple names of the cache associated with the annotated method:
@Cacheable("books")
or @Cacheable({"books", "publisher"})
For multiple names case, each of the caches will be checked before executing the method - if at least one cache is hit, then the associated value will be returned. All the other caches that do not contain the value will be updated as well even though the cached method was not actually executed.
Default key generation
Since caches are essentially key-value stores, each invocation of a cached method needs to be translated into a suitable
key for cache access. By default, the caching abstraction uses a simple KeyGenerator
based on the following
algorithm:
- if no parameter is given, return
SimpleKey.EMPTY
- if only one parameter is given, return that instance
- if multiple parameters are given, return a
SimpleKey
containing all parameters
This approach works well for most cases as long as parameters have natural keys and implement valid hashCode()
and equals()
.
To provide custom key generator, one needs to implement KeyGenerator
.
key/keyGenerator attribute
@Cacheable allows the user pick up arbitrary method arguments or their nested properties via key/keyGenerator attribute with the help of SpringEL to specify how the key is generated. For example,
@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
@Cacheable(cacheNames="books", keyGenerator="myKeyGenerator")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
Cache Resolution
Simple cache resolver is used to retrieve the cache defined at the operation level using the configured CacheManager
.
CacheResolver
can be used to achieve custom cache resolution.
@Cacheable(cacheNames="books", cacheManager="anotherCacheManager")
public Book findBook(ISBN isbn) {...}
@Cacheable(cacheResolver="runtimeCacheResolver")
public Book findBook(ISBN isbn) {...}
Sync cache
sync
attribute can be used to instruct the underlying cache provider to lock the cache entry while the value is being
computed. As a result, only one thread will be busy computing the value while the others are blocked until the entry
is updated in the cache.
Note: some cache implementation may not support it.
Conditional caching
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback")
public Book findBook(String name)
condition
attribute takes SpringEL expression that is evaluated to either true or false. If true, the method is cached.
unless
expression is evaluated after method executed, above example means only paperback books will be cached.
@CachePut
It is similar @Cacheable
, the difference is that @CachePut
always lets the method be executed and its result placed
into the cache.
While for @Cacheable
, its method won’t be run if cache key is the same until cache expires or gets flushed.
@CacheEvict
Remove data from cache. One can also indicate whether the eviction should occur after(the default) or before method
execution through the beforeInvocation
attribute.
@Caching
@Caching
allows multiple nested @Cacheable
, @CachePut
and @CacheEvict
to be used on the same method.
@CacheConfig
It is a class-level annotation that allows to share the cache names, the custom key generator, the custom cache manager and custom cache resolver. But it does not turn on any cache operation.
@EnableCaching
To enable above annotations work, one need to
- add
@EnableCaching
to one of your@Configuration
class - or add
<cache:annotation-driven />
to your spring xml file
The default advice mode for processing caching annotations is “proxy” which allows for interception of calls through the proxy only. Local calls within the same class cannot get intercepted thay way. For a more advanced mode of interception, switch to “aspectj” mode in combination with compile-time or load-time weaving.
<cache:annotation-driven />
only looks for @Cacheable/@CachePut/@CacheEvict/@Caching on beans in the same application context it is defined in. This means that, if you put<cache:annotation-driven />
in a WebApplicationContext for a DispatcherServlet, it only checks for beans in your controllers, not your services.
Method visibility and cache annotation
When using proxy, you should apply the cache annotations only to methods with public
visibility. If you do annotate them
on non-public method, no error is raised, but the annotated method does not exhibit the configured caching settings.
Consider AspectJ if you want use them on non-public method.
Spring recommends that you only annotate concerete class and methods of concerete class. You certainly can place the @Cache* annotation on an interface (or an interface method), but this works only as you would expect it to if you are using interface-based proxies. The fact that Java annotations are not inherited from interfaces means that if you are using class-based proxies ( proxy-target-class=”true”) or the weaving-based aspect ( mode=”aspectj”), then the caching settings are not recognized by the proxying and weaving infrastructure, and the object will not be wrapped in a caching proxy, which would be decidedly bad.
In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation, in effect, a method within the target object calling another method of the target object, will not lead to an actual caching at runtime even if the invoked method is marked with @Cacheable - considering using the aspectj mode in this case. Also, the proxy must be fully initialized to provide the expected behaviour so you should not rely on this feature in your initialization code, i.e. @PostConstruct.