Monday, January 21, 2013

Some more fun with @Cacheable


Let's say we configure a cache for methods for a third-party service class that is not under your direct control via AOP (as discussed in the previous post).
    <cache:advice id="externalFibonacciCacheAdvice" cache-manager="externalConcurrentMapFibonacciCacheManager">
        <cache:caching>
            <cache:cacheable method="*" cache="externalConcurrentMapFibonacci" />
        </cache:caching>
    </cache:advice>
Now say that this class has another method similar to getFibonacci(int):long as getNonFibonacci(int):long which returns a non-fibonacci constant number of -1.
    /**
     * Constantly returns -1
     * 
     * @param index
     * @return
     */
    public long getNonFibonacci(int index) {
        return -1;
    }
Now, I will modify the driver as:
        long fibonacci = ((ExternalFibonacciServiceImpl) externalFibonacciService)
                .getFibonacci(45);
        LOG.info("Fibonacci number is " + fibonacci);
        long nonFibonacci = ((ExternalFibonacciServiceImpl) externalFibonacciService)
                .getNonFibonacci(45);
        LOG.info("Non-fibonacci number is " + nonFibonacci);
What is the output?
[ 2013-01-21 21:21:48,496 [main] driver.SpringCacheTesterDriver.main():36  INFO ]: Fibonacci number is 1836311903
[ 2013-01-21 21:21:48,501 [main] driver.SpringCacheTesterDriver.main():39  INFO ]: Non-fibonacci number is 1836311903
What the hell?! Further investigation led me to SPR-8933 where it seems that this was by design. The default key generator only takes the method arguments into account (which seems to make sense when you define your services close to your domain objects - but goes awfully awry when you have a facade service like ReferenceDataService which will vend out data like countries as well as currencies). 
Thankfully there is a workaround (and a nice way to introduce a feature) - enter key-generator. The default key generator implementation used is org.springframework.cache.interceptor.DefaultKeyGenerator. We can define a custom MethodAwareCacheKeyGenerator that extends the DefaultKeyGenerator and takes the method name also into account. Just beanify it and use it
    <bean id="methodAwareCacheKeyGenerator" class="org.springframework.cache.interceptor.MethodAwareCacheKeyGenerator" />

    <cache:advice id="externalFibonacciCacheAdvice" cache-manager="externalConcurrentMapFibonacciCacheManager"
        key-generator="methodAwareCacheKeyGenerator">
        <cache:caching>
            <cache:cacheable method="*" cache="externalConcurrentMapFibonacci" />
        </cache:caching>
    </cache:advice>
And voila...
[ 2013-01-21 21:40:17,419 [main] driver.SpringCacheTesterDriver.main():36  INFO ]: Fibonacci number is 1836311903
[ 2013-01-21 21:40:17,419 [main] driver.SpringCacheTesterDriver.main():39  INFO ]: Non-fibonacci number is -1
It is important to note that though the problem was demonstrated with the AOP route, it can also easily happen if you define it via annotations on methods having same datatypes.
References:
  • https://jira.springsource.org/browse/SPR-8933
  • Code available at https://github.com/kilokahn/spring-testers/tree/master/spring-cache-tester

No comments:

Post a Comment