forked from jboyens/grails-springcache
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0352a68
commit 9c13f64
Showing
21 changed files
with
472 additions
and
867 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Springcache Plugin | ||
|
||
The _Springcache_ plugin allows you to easily add the following functionality to your Grails project: | ||
|
||
* Caching of Spring bean methods (typically Grails service methods). | ||
* Caching of page fragments generated by Grails controllers. | ||
* Cache flushing when Spring bean methods or controller actions are invoked. | ||
|
||
The plugin depends on the [EhCache][2] and [EhCache-Web][3] libraries. | ||
|
||
Full documentation can be found [here][1]. | ||
|
||
[1]:http://robfletcher.github.com/grails-springcache | ||
[2]:http://ehcache.org/ | ||
[3]:http://ehcache.org/documentation/web_caching.html |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
The _Springcache_ plugin allows you to easily add the following functionality to your Grails project: | ||
|
||
* Caching of Spring bean methods (typically Grails service methods). | ||
* Caching of page fragments generated by Grails controllers. | ||
* Cache flushing when Spring bean methods or controller actions are invoked. | ||
|
||
The plugin depends on the "EhCache":http://ehcache.org/ and "EhCache-Web":http://ehcache.org/documentation/web_caching.html libraries. | ||
|
||
h3. Contact | ||
|
||
The plugin code is hosted on [GitHub|http://github.com/robfletcher/grails-springcache]. Please feel free to fork the plugin and contribute patches. | ||
|
||
Please raise defects or enhancements against the Grails Springcache plugin component on the [Codehaus JIRA|http://jira.codehaus.org/browse/GRAILSPLUGINS/component/14010]. | ||
|
||
Questions, comments? [rob@energizedwork.com|mailto:rob@energizedwork.com] or better still contact me via the [Grails User mailing list|http://grails.org/Mailing+lists]. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
h4. 1.2 | ||
|
||
* Adds page fragment caching via annotations on controllers. | ||
* Simplifies config by getting rid of caching and flushing models and having annotations refer to cache names directly. | ||
* Adds configurable cache defaults that apply to configured caches and auto-created caches | ||
* Removes pluggable cache implementation in favour of using EhCache. | ||
|
||
h4. 1.1.3 | ||
|
||
* Fixes bug where an expired ehcache element whose key is still in the cache can cause the plugin to think the key still maps to a valid value. | ||
* Allows configuration of ehcache caches directly in @Config.groovy@ | ||
|
||
h4. 1.1.2 | ||
|
||
* Automatically create ehcache caches if they are not explicitly configured in @ehcache.xml@ | ||
|
||
h4. 1.1.1 | ||
|
||
* Fixes bug where plugin crashes if disabled | ||
|
||
h4. 1.1 | ||
|
||
* Complete rewrite to support Grails 1.2 and Spring 3.0. | ||
* Requires Grails 1.2+ | ||
|
||
h4. 1.0.1 | ||
|
||
* Fixes bug where plugin causes crash if disabled when debug logging is switched on. | ||
* Fixes compatibility with Java 1.5. | ||
|
||
h4. 1.0 | ||
|
||
* Configure alternate caching providers via @Config.groovy@ rather than having to override bean definitions in @resources.groovy@ | ||
* Removed dependency on joda-time which was only there for testing | ||
* Better synchronization for getting caches from the mapcache CacheManager | ||
|
||
h4. 0.2 | ||
|
||
* Configure caching and flushing models via @Config.groovy@ | ||
* Flag to disable plugin entirely for testing environments | ||
|
||
h4. 0.1 | ||
|
||
* Initial release |
16 changes: 16 additions & 0 deletions
16
src/docs/guide/2. The Cacheable and CacheFlush Annotations.gdoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
The _Springcache_ plugin provides two annotations that are the basis of how you can apply caching and flushing behaviour to both Spring bean methods and page fragments. Both annotations are in the @grails.plugin.springcache.annotations@ package. | ||
|
||
h3. The @\@Cachable@ annotation | ||
|
||
The @Cacheable@ annotation is applied to methods on Spring managed beans such as Grails services to cache method results or to controller actions to cache page fragments. The annotation requires a single argument which is the name of the cache that will be used. | ||
|
||
h3. The @\@CacheFlush@ annotation | ||
|
||
The @CacheFlush@ annotation can be applied in the same places as the @Cacheable@ annotation but instead of caching results it will cause a cache or set of caches to be flushed. The @CacheFlush@ annotation can take a single argument or a String array. Either way the arguments can simply be literal cache names or regular expression patterns that may match multiple cache names. For example: | ||
|
||
{code} | ||
@CacheFlush("myCache") | ||
@CacheFlush(/\w+ControllerCache/) | ||
@CacheFlush(["cacheA", "cacheB", "cacheC"]) | ||
@CacheFlush([/cache[A-Z]/, "myCache"]) | ||
{code} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
The typical use case for method caching is when you have Grails service methods that invoke expensive operations such as HTTP gets, web service calls, filesystem IO, etc. | ||
|
||
{note} | ||
Although you _can_ use the _Springcache_ plugin to cache service methods that query or update _GORM_ domain objects you should consider whether it's more appropriate to use the Hibernate 2nd level cache (see the relevant sections in the Grails documentation). In some cases using _Springcache_ does make sense, e.g. a service that aggregates the results of multiple queries. | ||
{note} | ||
|
||
Simply add an @\@Cacheable@ annotation to methods that should cache their results and a @\@CacheFlush@ annotation to methods that should flush caches. | ||
|
||
{note} | ||
Be aware that the annotations will only have any effect on Spring-managed beans. If you create instances of your class directly rather than getting them from the application context they will not be decorated with caching/flushing behaviour. | ||
{note} | ||
|
||
A simple example might be: | ||
|
||
h4. PiracyService.groovy | ||
|
||
{code} | ||
@Cacheable("pirateCache") | ||
def getPirates() { | ||
// return a list of pirates | ||
} | ||
|
||
@Cacheable("pirateCache") | ||
def findPirates(name) { | ||
// return a particular pirate | ||
} | ||
|
||
@Cacheable("shipCache") | ||
def getShips() { | ||
// return a list of ships | ||
} | ||
|
||
@CacheFlush("pirateCache") | ||
void registerNewPirate(Pirate sailor) { | ||
// store a new pirate | ||
} | ||
|
||
@CacheFlush("shipCache") | ||
void registerNewShip(Ship ship) { | ||
// store a new ship | ||
} | ||
|
||
@CacheFlush(["pirateCache", "shipCache"]) | ||
void registerNewShipWithCrew(Ship ship, Collection<Sailor> crew) { | ||
// store a new ship and associated pirates | ||
} | ||
{code} | ||
|
||
This ties the flushes on the _register_ methods to the particular caches they affect, so after calling @registerNewPirate@ the methods @getPirates@ and @findPirates@ will re-populate their cached results but @getShips@ would still use any cached results from previous calls. Calling @registerNewShipWithCrew@ will flush both caches. | ||
|
||
It is fine for multiple methods to share the same caches. Both @getPirates@ and @findPirates@ in the example above share the same cache. Cache entries are keyed on target object (the service instance in this case), method name and call parameters so there should be no confusion when using the same caches on multiple methods. | ||
|
||
There are various strategies you can adopt in naming and grouping caches, this example shouldn't be seen as definitive. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
When a @\@Cacheable@ annotation is found on a service method the plugin generates a key using: | ||
|
||
* The _target_ object, i.e. the service being called. | ||
* The service method name. | ||
* All method parameters. | ||
|
||
Since Grails services are typically Spring singletons the target object is not usually an issue. There's no need to implements _equals_ or _hashCode_ on your service classes unless you are using a different Spring bean scope and need to differentiate between calls made to different instances of the service. | ||
|
||
It is, however, *vital* to ensure that _equals_ and _hashCode_ is properly implemented on all the types used as parameters to cached methods. If this is not done it is very unlikely that the cache will ever be hit. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
The @\@Cacheable@ and @\@CacheFlush@ annotations can be applied to controller actions and the plugin will then cache the page fragment generated by the controller whether this is done by rendering a GSP, using a _MarkupBuilder_ closure, rendering text directly or whatever. Only successful page renders are cached, so redirects, 404s, errors and so on will not be. | ||
|
||
Composing pages so that they can be optimally cached requires some thought. The plugin uses a servlet filter that runs 'inside' the SiteMesh filter provided by Grails. This means that cached output is decorated by SiteMesh and the resulting page can therefore contain uncached content from the SiteMesh template. In addition you can use caching at a modular level to cache the output of controller actions invoked using the @g:include@ tag. Combining these techniques leads to powerful modular page caching. For example, you can cache the output of the 'main' controller then use @g:include@ tags in the SiteMesh layout to include content on the page that is cached separately - and can be flushed separately - from the main body of the page. |
51 changes: 51 additions & 0 deletions
51
src/docs/guide/4.1. Caching and Flushing with Controller Actions.gdoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
h3. Example: caching Grails CRUD pages | ||
|
||
Grails' standard scaffolded _CRUD_ pages provide a good example of how caching and flushing can be applied. For example, let's take an _Album_ domain class. The scaffolded controller could be annotated like this: | ||
|
||
h4. AlbumController.groovy | ||
|
||
{code} | ||
class AlbumController { | ||
// the index action is uncached as it just performs a redirect to list | ||
def index = { | ||
redirect(action: "list", params: params) | ||
} | ||
|
||
@Cacheable("albumControllerCache") | ||
def list = { | ||
// standard Grails scaffolding code omitted | ||
} | ||
|
||
@Cacheable("albumControllerCache") | ||
def create = { | ||
// standard Grails scaffolding code omitted | ||
} | ||
|
||
@CacheFlush(["albumControllerCache", "artistControllerCache", "latestControllerCache", "popularControllerCache"]) | ||
def save = { | ||
// standard Grails scaffolding code omitted | ||
} | ||
|
||
@Cacheable("albumControllerCache") | ||
def show = { | ||
// standard Grails scaffolding code omitted | ||
} | ||
|
||
@Cacheable("albumControllerCache") | ||
def edit = { | ||
// standard Grails scaffolding code omitted | ||
} | ||
|
||
@CacheFlush(["albumControllerCache", "latestControllerCache", "popularControllerCache"]) | ||
def update = { | ||
// standard Grails scaffolding code omitted | ||
} | ||
|
||
@CacheFlush(["albumControllerCache", "artistControllerCache", "latestControllerCache", "popularControllerCache"]) | ||
def delete = { | ||
// standard Grails scaffolding code omitted | ||
} | ||
} | ||
{code} | ||
|
||
The _list, show, create_ and _edit_ pages are all cached. The _show_ and _edit_ rely on an domain object id parameter and this will be included in the cache key so that @/album/show/1@ and @/album/show/2@ are cached separately. The _save, update_ and _delete_ actions will flush caches. Note that in addition to flushing the cache used by the _list, show, create_ and _edit_ actions they are flushing other caches which are content caches for controllers whose output should be refreshed if @Album@ data changes. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
h3. Example: decorating a cached page with dynamic content using SiteMesh | ||
|
||
It is often necessary to have portions of a page be dynamic. A typical example is when something is displayed to logged in users that will be different for each user. Those sorts of page sections are not really candidates for caching. At the same time other parts of the page may well be able to take advantage of caching. For example, if you want to display a _"Welcome back $username"_ type message in page headers while caching the main body of the page you can use SiteMesh templates like this: | ||
|
||
h4. grails-app/views/layouts/main.gsp | ||
|
||
{code} | ||
<html> | ||
<head> | ||
<title><g:layoutTitle default="Welcome to My Grails Application"/></title> | ||
<%-- render the page head from the controller - may be cached --%> | ||
<g:layoutHead/> | ||
</head> | ||
<body> | ||
<%-- render a "welcome back" header (tags used here are from the Spring Security plugin) --%> | ||
<g:isLoggedIn> | ||
<div id="loggedInUser"><g:message code="auth.loggedInAs" args="[loggedInUsername()]" default="Logged in as {0}"/></div> | ||
</g:isLoggedIn> | ||
<g:isNotLoggedIn> | ||
<div id="loginLink"><g:link controller="login"><g:message code="default.login.label" default="Login here"/></g:link></div> | ||
</g:isNotLoggedIn> | ||
|
||
<%-- render the page body from the controller - may be cached --%> | ||
<g:layoutBody/> | ||
</body> | ||
</html> | ||
{code} | ||
|
||
If the controller action invoked uses @\@Cacheable@ everything will work fine because the content of the SiteMesh layout is _not_ cached - only the content generated by the cached action. The SiteMesh template is applied to cached and uncached content alike so the correct username will be displayed to your users even though the main body of the page may have been loaded from a cache. | ||
|
||
h3. Example: a modular page using multiple cached sections | ||
|
||
One of the most powerful features of page fragment caching is that the generated page can be composed from multiple cached sections. This is accomplished using Grails' @g:include@ tag. For example, in this page the main body of the page is rendered by some controller action and the output of other controllers are included in the SiteMesh layout using the @g:include@ tag: | ||
|
||
h4. grails-app/views/layouts/main.gsp | ||
|
||
{code} | ||
<html> | ||
<head> | ||
<title><g:layoutTitle default="Welcome to My Grails Application"/></title> | ||
<%-- render the page head from the controller - may be cached --%> | ||
<g:layoutHead/> | ||
</head> | ||
<body> | ||
<%-- render the page body from the controller - may be cached --%> | ||
<g:layoutBody/> | ||
|
||
<div class="sidebar"> | ||
<%-- each of these controller actions can be cached separately as well --%> | ||
<g:include controller="latest" action="albums"/> | ||
<g:include controller="popular" action="albums"/> | ||
</div> | ||
</body> | ||
</html> | ||
{code} | ||
|
||
h4. LatestController.groovy | ||
|
||
{code} | ||
@Cacheable("latestAlbums") | ||
def albums = { | ||
def albums = Album.list(sort: "dateCreated", order: "desc", max: 10) | ||
[albumInstanceList: albums] | ||
} | ||
{code} | ||
|
||
h4. LatestController.groovy | ||
|
||
{code} | ||
@Cacheable("popularAlbums") | ||
def albums = { | ||
def albums = Album.listOrderByAverageRating(max: 10) | ||
return [albumInstanceList: albums] | ||
} | ||
{code} | ||
|
||
If all the caches are hit the final rendered page will be composed of 3 separate cached sections. What is more, each individual section can be flushed without affecting the others so with some thought about how to compose your page and apply your caches you can optimise cache usage without delivering stale data to the user. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
The @\@Cacheable@ and @\@CacheFlush@ annotations can be applied to controllers at class level. This is more likely useful with @\@Cacheable@ but it is certainly possible to apply @\@CacheFlush@ at class level so that any action on that controller will flush a set of caches. Any annotation on an individual action will be applied in preference to an annotation at class level, so a class level annotation behaves like a default. An annotation at class level will work with dynamic scaffolded actions so you don't have to generate a concrete action in order to benefit from caching behaviour. | ||
|
||
{code} | ||
@Cacheable("albumControllerCache") | ||
class AlbumController { | ||
|
||
static scaffold = true // all dynamically scaffolded actions will be cached | ||
|
||
@Cacheable("albumListCache") | ||
def list = { | ||
// ... | ||
} | ||
|
||
@CacheFlush(/album\w+Cache/) | ||
def save = { | ||
// ... | ||
} | ||
|
||
def show = { | ||
// ... | ||
} | ||
} | ||
{code} | ||
|
||
In this example: | ||
|
||
* The _show_ action will use the default class level @\@Cacheable@ annotation and its page fragment will be cached in the _albumControllerCache_ cache. | ||
* The _list_ action will not use the default as it specifies its own @\@Cacheable@ annotation and its content will be cached separately. | ||
* The _save_ action uses a @\@CacheFlush@ and will therefore not be cached at all. | ||
* Dynamically scaffolded actions (e.g. _edit_, _update_, etc.) will use the class level annotation and their results will be cached in the _albumControllerCache_ cache. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
By default page fragment cache entries are keyed on controller name, action name and any request parameters (which can be from a query string, _POST_ body or those added by Grails URL mappings, e.g. the _id_ parameter on a standard _show_ or _edit_ action). If you need to use some kind of special key generation you can implement the interface @grails.plugin.springcache.web.key.KeyGenerator@ (or extend @grails.plugin.springcache.web.key.AbstractKeyGenerator@ or one of the existing implementations that the plugin provides) then simply override the Spring bean property on the filter in @Config.groovy@ like this: | ||
|
||
{code} | ||
beans { | ||
springcacheFilter { | ||
keyGenerator = new MyKeyGenerator() | ||
} | ||
} | ||
{code} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
By default the key generator used by the page fragment caching filter does not take content negotiation into account. However, if you are caching controller actions that use Grails' "@withFormat@":http://grails.org/doc/latest/ref/Controllers/withFormat.html dynamic method to render different content types you will want to cache results separately according to the output format. The plugin provides a key generator implementation that supports this, you just need to override the filter's key generator in @Config.groovy@ like this: | ||
|
||
{code} | ||
import grails.plugin.springcache.web.key.MimeTypeAwareKeyGenerator | ||
|
||
beans { | ||
springcacheFilter { | ||
keyGenerator = new MimeTypeAwareKeyGenerator() | ||
} | ||
} | ||
{code} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
The plugin only provides page fragment caching rather than full page caching. Full page caching is very simple to apply using the _EhCache-Web_ library that the Springcache plugin uses. See my blog post "here":http://adhockery.blogspot.com/2010/02/full-page-caching-in-grails-with.html for details. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
Both the servlet filter used for content caching and the AOP aspects used for service method caching use a Grails service to handle caching and flushing. Your application can access this service directly if you need to do any programmatic caching or flushing. The service is called _springcacheService_ and can be auto-injected into your Grails artefacts just like any other Spring bean. The service provides the following methods: | ||
|
||
* *doWithCache(String, Serializable, Closure)* : Parameters are cache name, cache key and closure invoked when there is no cached value. The method returns either the cached value or the return value of the closure. If the closure is invoked the return value is cached. | ||
* *doWithBlockingCache(String, Serializable, Closure)* : A variant of doWithCache that ensures a BlockingCache is used and handles exceptions so that the cache's lock is relinquished correctly. | ||
* *flush(patterns)* : Flushes all caches matching the specified names/patterns. The parameter can be a String, a regex pattern or a Collection or array of them. | ||
* *flushAll()* : Flushes all caches. | ||
* *clearStatistics()* : Clears statistics for all caches. | ||
* *getOrCreateCache(name)* : Gets the named cache or creates it from defaults if it does not exist. | ||
* *getOrCreateBlockingCache(name)* : As _getOrCreateCache_ but will decorate the cache with a "BlockingCache":http://ehcache.org/apidocs/net/sf/ehcache/constructs/blocking/BlockingCache.html if it is non-blocking. | ||
|
||
The plugin encourages you to use declarative caching and flushing to maintain a good separation of concerns. Over-using the _springcacheService_ is likely to render your code harder to test and maintain. That said programmatic caching may be necessary in some places but there are some caveats: | ||
|
||
* If you try to perform caching or flushing in interceptors on controller actions bear in mind those actions, and therefore any interceptors, will not be invoked at all if they are annotated with @\@Cacheable@ and the cache is hit. | ||
* Controller actions don't _return_ HTML output so you can't do fine grained content caching by using @springcacheService.doWithCache@ in a controller action. |
Oops, something went wrong.