Since version 3.1, Spring Framework provides support for transparently
adding caching into an existing Spring application. Similar to the
transaction
support, the caching abstraction allows consistent use of various caching
solutions with minimal impact on the code.
At its core, the abstraction applies caching to Java methods, reducing thus the number of executions based on the
information available in the cache. That is, each time a
targeted
method is invoked, the abstraction
will apply a caching behavior checking whether the method has been already executed for the given arguments. If it has,
then the cached result is returned without having to execute the actual method; if it has not, then method is executed, the
result cached and returned to the user so that, the next time the method is invoked, the cached result is returned.
This way, expensive methods (whether CPU or IO bound) can be executed only once for a given set of parameters and the result
reused without having to actually execute the method again. The caching logic is applied transparently without any interference
to the invoker.
Important
Obviously this approach works only for methods that are guaranteed to return the same output (result) for a given input
(or arguments) no matter how many times it is being executed.
To use the cache abstraction, the developer needs to take care of two aspects:
caching declaration - identify the methods that need to be cached and their policy
cache configuration - the backing cache where the data is stored and read from
Note that just like other services in Spring Framework, the caching service is an abstraction (not a cache implementation) and requires
the use of an actual storage to store the cache data - that is, the abstraction frees the developer from having to write the caching
logic but does not provide the actual stores. There are two integrations available out of the box, for JDK
java.util.concurrent.ConcurrentMap
and
EhCache
- see
Section 29.6, “Plugging-in different back-end caches”
for more information on plugging in other cache stores/providers.
Below are some examples of various SpEL declarations - if you are not familiar with it, do yourself a favour and read
Chapter 8,
Spring Expression Language (SpEL)
:
@Cacheable(value=
"books"
,
key="#isbn"
public
Book findBook(ISBN isbn,
boolean
checkWarehouse,
boolean
includeUsed)
@Cacheable(value=
"books"
,
key="#isbn.rawNumber"
)
public
Book findBook(ISBN isbn,
boolean
checkWarehouse,
boolean
includeUsed)
@Cacheable(value=
"books"
,
key="T(someType).hash(#isbn)"
)
public
Book findBook(ISBN isbn,
boolean
checkWarehouse,
boolean
includeUsed)
The snippets above, show how easy it is to select a certain argument, one of its properties or even an arbitrary (static) method.
Each
SpEL
expression evaluates again a dedicated
context
. In addition
to the build in parameters, the framework provides dedicated caching related metadata such as the argument names. The next table lists the items made available to the context
so one can use them for key and conditional(see next section) computations:
The arguments (as array) used for invoking the target
#root.args[0]
caches
root object
Collection of caches against which the current method is executed
#root.caches[0].name
argument name
evaluation context
Name of any of the method argument. If for some reason the names are not available (ex: no debug information),
the argument names are also available under the
a<#arg>
where
#arg
stands for the argument index (starting from 0).
iban
or
a0
(one can also use
p0
or
p<#arg>
notation as an alias).
result
evaluation context
The result of the method call (the value to be cached). Only available in '
unless
' expressions and '
cache evict
'
expression (when
beforeInvocation
is
false
).
#result
For cases where the cache needs to be updated without interfering with the method execution, one can use the
@CachePut
annotation. That is, the method will always
be executed and its result placed into the cache (according to the
@CachePut
options). It supports the same options as
@Cacheable
and should be used
for cache population rather then method flow optimization.
Note that using
@CachePut
and
@Cacheable
annotations on the same method is generally discouraged because they have different behaviors. While the latter
causes the method execution to be skipped by using the cache, the former forces the execution in order to execute a cache update. This leads to unexpected behavior and with the exception of specific
corner-cases (such as annotations having conditions that exclude them from each other), such declarations should be avoided.
Name of cache manager to use. Only required
if the name of the cache manager is not
cacheManager, as in the example
above.
mode
mode
proxy
The default mode "proxy" processes annotated
beans to be proxied using Spring's AOP framework (following
proxy semantics, as discussed above, applying to method calls
coming in through the proxy only). The alternative mode
"aspectj" instead weaves the affected classes with Spring's
AspectJ caching aspect, modifying the target class byte
code to apply to any kind of method call. AspectJ weaving
requires spring-aspects.jar in the classpath as well as
load-time weaving (or compile-time weaving) enabled. (See
the section called “Spring configuration” for details on how to set
up load-time weaving.)
proxy-target-class
proxyTargetClass
false
Applies to proxy mode only. Controls what type of
caching proxies are created for classes annotated with
the @Cacheable or @CacheEvict annotations.
If the proxy-target-class attribute is set
to true, then class-based proxies are
created. If proxy-target-class is
false or if the attribute is omitted, then
standard JDK interface-based proxies are created. (See Section 9.6, “Proxying mechanisms” for a detailed examination of the
different proxy types.)
order
order
Ordered.LOWEST_PRECEDENCE
Defines the order of the cache advice that
is applied to beans annotated with
@Cacheable or @CacheEvict.
(For more
information about the rules related to ordering of AOP advice,
see the section called “Advice ordering”.) No
specified ordering means that the AOP subsystem determines the
order of the advice.
Method visibility and
@Cacheable/@CachePut/@CacheEvict
When using proxies, you should apply the
@Cache* annotations only to
methods with public visibility. If you do
annotate protected, private or package-visible methods with these annotations,
no error is raised, but the annotated method does not exhibit the configured
caching settings. Consider the use of AspectJ (see below) if you
need to annotate non-public methods as it changes the bytecode itself.
Spring recommends that you only annotate concrete classes (and
methods of concrete classes) with the
@Cache* annotation, as opposed
to annotating interfaces. 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.
Above, we have defined our own SlowService annotation which itself is annotated with @Cacheable - now we can replace the following code:
@Cacheable(value="books", key="#isbn")public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
with:
@SlowServicepublic Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
Even though @SlowService is not a Spring annotation, the container automatically picks up its declaration at runtime and understands its meaning. Note that as
mentioned above, the annotation-driven behavior needs to be enabled.
<!-- the service we want to make cacheable --><beanid="bookService"class="x.y.service.DefaultBookService"/><!-- cache definitions --><cache:adviceid="cacheAdvice"cache-manager="cacheManager"><cache:cachingcache="books"><cache:cacheablemethod="findBook"key="#isbn"/><cache:cache-evictmethod="loadBooks"all-entries="true"/></cache:caching></cache:advice><!-- apply the cacheable behavior to all BookService interfaces --><aop:config><aop:advisoradvice-ref="cacheAdvice"pointcut="execution(* x.y.BookService.*(..))"/></aop:config>
// cache manager definition omitted
In the configuration above, the bookService is made cacheable. The caching semantics to apply are encapsulated in the cache:advice definition which
instructs method findBooks to be used for putting data into the cache while method loadBooks for evicting data. Both definitions are working against the
books cache.
The aop:config definition applies the cache advice to the appropriate points in the program by using the AspectJ pointcut expression (more information is available
in Chapter 9, Aspect Oriented Programming with Spring). In the example above, all methods from the BookService are considered and the cache advice applied to them.
The declarative XML caching supports all of the annotation-based model so moving between the two should be fairly easy - further more both can be used inside the same application.
The XML based approach does not touch the target code however it is inherently more verbose; when dealing with classes with overloaded methods that are targeted for caching, identifying the
proper methods does take an extra effort since the method argument is not a good discriminator - in these cases, the AspectJ pointcut can be used to cherry pick the target
methods and apply the appropriate caching functionality. However through XML, it is easier to apply a package/group/interface-wide caching (again due to the AspectJ pointcut) and to create
template-like definitions (as we did in the example above by defining the target cache through the cache:definitions cache attribute).