I've been plugging away for a few days on adding caching to Grails services. At first I tried to go down the road of decorating the services' metaClasses but since Grails uses an AOP proxy to add transactional behaviour to services this wouldn't work - the metaClass I got from the injected service object was not the same one my plugin had added stuff to. Also annotations on the service class were unavailable from the proxy.
The Spring Modules project has a nice caching implementation that uses annotations to decorate methods with caching and flushing behaviour. Getting this to work with Grails, was reasonably straightforward. As it turns out one additional config line made the difference between the spring-modules example config and something that worked in Grails.
I based the config on the annotation example from the Spring Modules documentation, converting the Spring XML to Grails BeanBuilder format:
import org.springframework.aop.framework.autoproxy.*The vital bit is the
import org.springmodules.cache.annotations.*
import org.springmodules.cache.interceptor.caching.*
import org.springmodules.cache.interceptor.flush.*
def doWithSpring = {
// declaration of cacheManager and cacheProviderFacade omitted - implementation specific
autoproxy(DefaultAdvisorAutoProxyCreator) {
proxyTargetClass = true
}
cachingAttributeSource(AnnotationCachingAttributeSource)
cachingInterceptor(MetadataCachingInterceptor) {
cacheProviderFacade = ref("cacheProviderFacade")
cachingAttributeSource = ref("cachingAttributeSource")
def props = new Properties()
props.myCachingModel = 'cacheName=MY_CACHE_NAME'
cachingModels = props
}
cachingAttributeSourceAdvisor(CachingAttributeSourceAdvisor, ref("cachingInterceptor"))
flushingAttributeSource(AnnotationFlushingAttributeSource)
flushingInterceptor(MetadataFlushingInterceptor) {
cacheProviderFacade = ref("cacheProviderFacade")
flushingAttributeSource = ref("flushingAttributeSource")
def props = new Properties()
props.myFlushingModel = 'cacheNames=MY_CACHE_NAME'
flushingModels = props
}
flushingAttributeSourceAdvisor(FlushingAttributeSourceAdvisor, ref("flushingInterceptor"))
}
proxyTargetClass = true
on the autoproxy bean. I won't pretend to understand what that's doing - I just noticed that's how ServicesGrailsPlugin
was getting the transactional proxies to work.After that it's a simple case of annotating service methods (in fact methods on any Spring-managed bean but services are the obvious use case).
import org.springmodules.cache.annotations.*This works fine on transactional and non-transactional Grails services. You can see the cache operations by setting
@Cacheable(modelId = "myCachingModel")
def getSomethingInAnExpensiveWay(param1, param2) {
// ...
}
@CacheFlush(modelId = "myFlushingModel")
def updateSomething(param1, param2) {
// ...
}
log4j.logger.org.springmodules.cache = "trace"
.The actual caching implementation used can be anything - spring-modules supports ehcache, oscache, JBoss cache, JCS, etc. All that's required is to wire in the cacheManager and cacheProviderFacade beans as described in the Spring Modules documentation.
I've bundled the results up as a Grails plugin (grails install-plugin springcache
). It uses a ConcurrentHashMap
backed simple cache implementation by default but wiring in ehcache or oscache is easy.
Caching and flushing models are configured in Config.groovy, e.g.:
springcache {Particular caching implementations may have further options or require additional external config (such as ehcache's
cachingModels {
cachingModel1 = 'cacheName=CACHE_1'
cachingModel2 = 'cacheName=CACHE_2'
}
flushingModels {
flushingModel1 = 'cacheNames=CACHE_1,CACHE_2'
}
}
ehcache.xml
file).To disable the plugin you can set springcache.disabled=true
. For example, it may be desirable to disable the plugin in the test environment.
Edit: Documentation added on grails.org
3 comments:
Nice!
The reason you have to say proxy target class is because the service is not implementing an interface - so Spring has to be told.
I remember the good ol' days when Services implemented interfaces. ;-)
Yeah, because I love writing out all my method declarations twice.
Post a Comment