+ draft cache documentation
This commit is contained in:
Costin Leau 2010-12-21 17:13:17 +00:00
parent 893fc83f49
commit 29a8ca4edc
2 changed files with 209 additions and 0 deletions

View File

@ -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>

View File

@ -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 -->