parent
893fc83f49
commit
29a8ca4edc
|
|
@ -0,0 +1,198 @@
|
|||
<?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>
|
||||
|
|
@ -453,6 +453,12 @@
|
|||
<listitem>
|
||||
<para><xref linkend="dynamic-language" /></para>
|
||||
</listitem>
|
||||
|
||||
<!--
|
||||
<listitem>
|
||||
<para><xref linkend="cache" /></para>
|
||||
</listitem>
|
||||
-->
|
||||
</itemizedlist>
|
||||
</partintro>
|
||||
|
||||
|
|
@ -473,6 +479,11 @@
|
|||
|
||||
<xi:include href="dynamic-languages.xml"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||
|
||||
<!--
|
||||
<xi:include href="cache.xml"
|
||||
xmlns:xi="http://www.w3.org/2001/XInclude" />
|
||||
-->
|
||||
</part>
|
||||
|
||||
<!-- back matter -->
|
||||
|
|
|
|||
Loading…
Reference in New Issue