The easiest thing in the end was to manually create an xml to load the bean in question. (Once we created the bean in the xml, then the cachable annotations were recognized)
This worked in terms of loading the bean with the caching functionality built in, but then we began to run into class cast exceptions, because of the way that spring implements the caching (using proxys). See http://spring.io/blog/2012/05/23/transactions-caching-and-aop-understanding-proxy-usage-in-spring
The easiest solution we found to this, was to create an interface for the service in question. Then the proxying was able to cast the dynamically generated proxyClass to the interface.
Test xml (in test/unit)
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd">
class="org.springframework.cache.ehcache.EhCacheCacheManager" p:cache-manager-ref="ehcache"/>
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="TestEhCache.xml"/>
EhCache.xml (in grails-app/conf)
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
maxElementsInMemory='100'
overflowToDisk='false' />
maxElementsInMemory="100"
eternal="false"
timeToIdleSeconds="3600"
timeToLiveSeconds="0"
overflowToDisk="false"
memoryStoreEvictionPolicy="LFU"/>
Interface
import org.springframework.cache.annotation.Cacheable
interface MyServiceIF {
// calling stored procedure to determine the as_of_date
@Cacheable("priorToDate")
public Date priorToDate(String yyyymmdd);
}
Class
class MyService implements MyServiceIF {
static transactional = false
public Date priorToDate(String yyyymmdd) {
return evaluate(yyyymmdd, -1);
}
}
Spock Test
Note also that if you are declaring a method cacheable, with multiple parameters, then you may want to define a keyGenerator, (or ignore the params)
public void validateCache() {
given:
CacheManager cacheManager = ctx.getBean("cacheManager")
String result;
when:
Cache dateCache = cacheManager.getCache(testName);
String result1FromCache = dateCache.get(dateToTest); // Verify that the cache is empty
Object resultFromSds
Object result2FromSds
Object result2FromCache
if(testName =="futureBusinessDate" || testName == "pastBusinessDate"){
resultFromSds = dateToString(daoService."$testName"(dateToTest,1 ))
Object key = new DefaultKeyGenerator().generate(daoService, DalSdsDateIF.class.getMethod(testName, String.class, int.class), dateToTest, 1) //compund params, so must generate key
result2FromCache = dateToString(dateCache.get(key).get());
result2FromSds = dateToString(daoService."$testName"(dateToTest,1 ))
} else {
resultFromSds = dateToString(daoService."$testName"(dateToTest) )
result2FromCache = dateToString(dateCache.get(dateToTest).get());
result2FromSds = dateToString(daoService."$testName"(dateToTest) ) // expect this come from cache, so will not call log again
}
then:
dateCache!=null
result1FromCache==null //verify cache is empty
resultFromSds==expectedResult
result2FromCache==expectedResult
result2FromSds==expectedResult
count ==expectedCallsToLog // count number of calls to log.info.. Expect one per call, except for isBusinessDate
where:
testName | dateToTest | expectedResult | expectedCallsToLog
"priorToDate" | "2016-09-06" | "2016-09-02" | 1
"nextToDate" | "2016-09-02" | "2016-09-06" | 1
"futureBusinessDate"| "2016-09-02" | "2016-09-06" | 1
"pastBusinessDate" | "2016-09-06" | "2016-09-02" | 1
}
e.g. in the test
Object key = new DefaultKeyGenerator().generate(daoService, DalSdsDateIF.class.getMethod(testName, String.class, int.class), dateToTest, 1) //compund params, so must generate key
If you have parameters in the method call that you don't want influsencing the cahce (e.g. ignroe them you can do this, or this)
e.g. to ignore params you can do this
@Cacheable(value="myCache", key="#root.methodName")// Force key name to be fixed no matter what params passed in
public Map