198 lines
12 KiB
XML
198 lines
12 KiB
XML
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
||
|
|
<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
|
||
|
|
"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
|
||
|
|
|
||
|
|
<chapter id="cache">
|
||
|
|
<title>Cache Abstraction</title>
|
||
|
|
|
||
|
|
<section id="cache-introduction">
|
||
|
|
<title>Introduction</title>
|
||
|
|
|
||
|
|
<para>Since version 3.1, Spring Framework provides support for transparently
|
||
|
|
adding caching into an existing Spring application. Similar to the <link linkend="transaction">transaction</link>
|
||
|
|
support, the caching abstraction allows consistent use of various caching
|
||
|
|
solutions with minimal impact on the code.</para>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section id="cache-strategies">
|
||
|
|
<title>Understanding the cache abstraction</title>
|
||
|
|
|
||
|
|
<sidebar>
|
||
|
|
<title>Cache vs Buffer</title>
|
||
|
|
<para>The terms "buffer" and "cache" tend to be used interchangeably; note however they represent different things.
|
||
|
|
A buffer is used traditionally as an intermediate temporary store for data between a fast and a slow entity. As one
|
||
|
|
party would have to <emphasis>wait</emphasis> for the other affecting performance, the buffer alleviates this by
|
||
|
|
allowing entire blocks of data to move at once rather then in small chunks. The data is written and read only once from
|
||
|
|
the buffer. Further more, the buffers are <emphasis>visible</emphasis> to at least one party which is aware of it.</para>
|
||
|
|
<para>A cache on the other hand by definition is hidden and neither party is aware that caching occurs.It as well improves
|
||
|
|
performance but does that by allowing the same data to be read multiple times in a fast fashion.</para>
|
||
|
|
|
||
|
|
<para>A further explanation of the differences between two can be found
|
||
|
|
<ulink url="http://en.wikipedia.org/wiki/Cache#The_difference_between_buffer_and_cache">here</ulink>.</para>
|
||
|
|
</sidebar>
|
||
|
|
|
||
|
|
<para>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 <emphasis>targeted</emphasis> method is invoked, the 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 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.</para>
|
||
|
|
|
||
|
|
<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.</important>
|
||
|
|
|
||
|
|
<para>To use the cache abstraction, the developer needs to take care of two aspects:
|
||
|
|
<itemizedlist>
|
||
|
|
<listitem>caching declaration - identify the methods that need to be cached and their policy</listitem>
|
||
|
|
<listitem>cache configuration - the backing cache where the data is stored and read from</listitem>
|
||
|
|
</itemizedlist>
|
||
|
|
</para>
|
||
|
|
|
||
|
|
<para>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 <literal>java.util.concurrent.ConcurrentMap</literal>
|
||
|
|
and <ulink url="http://ehcache.org/">Ehcache</ulink> - see <xref linkend="cache-plug"/> for more information on plugging in other cache stores/providers.</para>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section id="cache-annotations">
|
||
|
|
<title>Declarative annotation-based caching</title>
|
||
|
|
|
||
|
|
<para>For caching declaration, the abstraction provides two Java annotations: <literal>@Cacheable</literal> and <literal>@CacheEvict</literal> which allow methods
|
||
|
|
to trigger cache population or cache eviction. Let us take a closer look at each annotation:</para>
|
||
|
|
|
||
|
|
<section id="cache-annotations-cacheable">
|
||
|
|
<title><literal>@Cacheable</literal> annotation</title>
|
||
|
|
|
||
|
|
<para>As the name implies, <literal>@Cacheable</literal> is used to demarcate methods that are cacheable - that is, methods for whom the result is stored into the cache
|
||
|
|
so on subsequent invocations (with the same arguments), the value in the cache is returned without having to actually execute the method. In its simplest form,
|
||
|
|
the annotation declaration requires the name of the cache associated with the annotated method:</para>
|
||
|
|
|
||
|
|
<programlisting language="java"><![CDATA[@Cacheable("books")
|
||
|
|
public Book findBook(ISBN isbn) {...}]]></programlisting>
|
||
|
|
|
||
|
|
<para>In the snippet above, the method <literal>findBook</literal> is associated with the cache named <literal>books</literal>. Each time the method is called, the cache
|
||
|
|
is checked to see whether the invocation has been already executed and does not have to be repeated. While in most cases, only one cache is declared, the annotation allows multiple
|
||
|
|
names to be specified so that more then one cache are being used. In this 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:</para>
|
||
|
|
<note>All the other caches that do not contain the method will be updated as well event though the cached method was not actually
|
||
|
|
executed.</note>
|
||
|
|
|
||
|
|
<programlisting language="java"><![CDATA[@Cacheable({ "books", "isbns" })
|
||
|
|
public Book findBook(ISBN isbn) {...}]]></programlisting>
|
||
|
|
|
||
|
|
<section id="cache-annotations-cacheable-default-key">
|
||
|
|
<title>Default Key Generation</title>
|
||
|
|
|
||
|
|
<para>Since caches are essentially key-value stores, each invocation of a cached method needs to be translated into a suitable key for cache access.
|
||
|
|
Out of the box, the caching abstraction uses a simple <literal>hash-code</literal> based <interfacename>KeyGenerator</interfacename> that computes the key based on the
|
||
|
|
hashes of all objects used for method invocation. This approach works well for objects with <emphasis>natural keys</emphasis> as long as
|
||
|
|
the <literal>hashCode()</literal> reflects that. If that is not the case then
|
||
|
|
for distributed or persistent environments, the strategy needs to be changed as the objects hashCode is not preserved.
|
||
|
|
In fact, depending on the JVM implementation or running conditions, the same hashCode can be reused for different objects, in the same VM instance.</para>
|
||
|
|
|
||
|
|
<para>To provide a different <emphasis>default</emphasis> key generator, one needs to implement the <interfacename>org.springframework.cache.KeyGenerator</interfacename> interface.
|
||
|
|
Once <link linkend="cache-configuration">configured</link>, the generator will be used for each declaration that doesn not specify its own key generation strategy (see below).
|
||
|
|
</para>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section id="cache-annotations-cacheable-key">
|
||
|
|
<title>Custom Key Generation Declaration</title>
|
||
|
|
|
||
|
|
<para>Since caching is generic, it is quite likely the target methods have various signatures that cannot be simply mapped on top of the cache structure. This tends to become
|
||
|
|
obvious when the target method has multiple arguments out of which only some are suitable for caching (while the rest are used only by the method logic). For example:</para>
|
||
|
|
|
||
|
|
<programlisting language="java"><![CDATA[@Cacheable("books")
|
||
|
|
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed]]></programlisting>
|
||
|
|
|
||
|
|
<para>At first glance, while the two <literal>boolean</literal> arguments influence the way the book is found, they are no use for the cache. Further more what if only one of the two
|
||
|
|
is important while the other is not?</para>
|
||
|
|
|
||
|
|
<para>For such cases, the <literal>@Cacheable</literal> annotation allows the user to specify how the key is generated through its <literal>key</literal> attribute.
|
||
|
|
The developer can use <link linkend="expressions">SpEL</link> to pick the arguments of interest (or their nested properties), perform operations or even invoke arbitrary methods without
|
||
|
|
having to write any code or implement any interface. This is the recommended approach over the <link linkend="cache-annotations-cacheable-default-key">default</link> generator since
|
||
|
|
methods tend to be quite different in signatures as the code base grows; while the default strategy might work for some methods, it rarely does for all methods.</para>
|
||
|
|
|
||
|
|
<para>
|
||
|
|
Below are some examples of various SpEL declarations - if you are not familiar with it, do yourself a favour and read <xref linkend="expressions"/>:
|
||
|
|
</para>
|
||
|
|
|
||
|
|
<programlisting language="java"><!-- select 'isbn' argument -->
|
||
|
|
@Cacheable(value="book", <emphasis role="bold">key="isbn"</emphasis>
|
||
|
|
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
|
||
|
|
|
||
|
|
<!-- select nested property of a certain argument -->
|
||
|
|
@Cacheable(value="book", <emphasis role="bold">key="isbn.rawNumber"</emphasis>)
|
||
|
|
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
|
||
|
|
|
||
|
|
<!-- invoke arbitrary method using certain arguments -->
|
||
|
|
@Cacheable(value="book", <emphasis role="bold">key="T(someType).hash(isbn)"</emphasis>)
|
||
|
|
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
|
||
|
|
</programlisting>
|
||
|
|
|
||
|
|
<para>The snippets above, show how easy it is to select a certain argument, one of its properties or even an arbitrary (static) method.</para>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section id="cache-spel-contex">
|
||
|
|
<title>Available caching <literal>SpEL</literal> evaluation context</title>
|
||
|
|
|
||
|
|
<para>Each <literal>SpEL</literal> expression evaluates again a dedicated <literal><link linkend="expressions-language-ref">context</link></literal>. 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:</para>
|
||
|
|
|
||
|
|
<table id="cache-spel-context-tbl" pgwide="1">
|
||
|
|
<title>Cache SpEL available metadata</title>
|
||
|
|
<tgroup cols="3">
|
||
|
|
<colspec align="center" />
|
||
|
|
<thead>
|
||
|
|
<row>
|
||
|
|
<entry>Name</entry>
|
||
|
|
<entry>Location</entry>
|
||
|
|
<entry>Description</entry>
|
||
|
|
<entry>Example</entry>
|
||
|
|
</row>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<row>
|
||
|
|
<entry>methodName</entry>
|
||
|
|
<entry>root object</entry>
|
||
|
|
<entry>The name of the method being invoked</entry>
|
||
|
|
<entry><screen>#root.methodName</screen></entry>
|
||
|
|
</row>
|
||
|
|
<row>
|
||
|
|
<entry>caches</entry>
|
||
|
|
<entry>root object</entry>
|
||
|
|
<entry>Collection of caches against which the current method is executed</entry>
|
||
|
|
<entry><screen>#root.caches[0].name</screen></entry>
|
||
|
|
</row>
|
||
|
|
<row>
|
||
|
|
<entry><emphasis>parameter name</emphasis></entry>
|
||
|
|
<entry>evaluation context</entry>
|
||
|
|
<entry>Name of any of the method parameter. If for some reason the names are not available (ex: no debug information),
|
||
|
|
the parameter names are also available under the <literal><![CDATA[p<#arg>]]></literal> where
|
||
|
|
<emphasis><![CDATA[#arg]]></emphasis> stands for the parameter index (starting from 0).</entry>
|
||
|
|
<entry><screen>iban</screen> or <screen>p0</screen></entry>
|
||
|
|
</row>
|
||
|
|
</tbody>
|
||
|
|
</tgroup>
|
||
|
|
</table>
|
||
|
|
</section>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section id="cache-annotations-evict">
|
||
|
|
|
||
|
|
</section>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section id="cache-configuration">
|
||
|
|
<title>Configuring the <interfacename>Cache</interfacename></title>
|
||
|
|
<para></para>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
<section id="cache-plug">
|
||
|
|
<title>Plugging-in different back-end caches</title>
|
||
|
|
<para></para>
|
||
|
|
</section>
|
||
|
|
|
||
|
|
</chapter>
|