Wednesday, June 11, 2008

Spring cache module catcha!

Caching using JDK 5.0 annotation with Spring seems to be easy. It has a catcha though, when you use the @Cacheable annotation in the second-level call of the object you want to cache. Let me explain it with an example. Consider this class:


class SampleRepositoryXmlImpl implements SampleRepository {
public Set getSamples(int n) {
Set samples = this.getSamplesFromDisk();
return getUpToNRandomSamplesFromSet(n, samples);
}

@Cacheable(groups="someCacheableGroup")
public Set getSamplesFromDisk() {
...
}
}


See, the innocent call to getSamplesFromDisk() from getSamples() should get invoked only when the return value expires in the cache. But let's see how Spring's JdkDynamicAopProxy works (excerpt from Spring's source code JdkDynamicAopProxy:189):


// Get the interception chain for this method.
List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
} else {
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}


The "if (chain.isEmpty)" condition block evaluates to true when getSamples() is invoked but then in the inner call to getSamplesFromDisk(), the same condition block evaluates to false since it determines that there is no more chain, thus invokes the method directly, ignoring the interceptor created by @Cacheable annotation!

For now, to avoid the problem, you should only tag methods as @Cacheable only in the most external level call of the object. If necessary, refactor your code and extract the method to a Strategy object.

Tuesday, June 3, 2008

OSCache + Spring Framework

Since I didn't find a good reference to using OSCache with Spring after browsing the web for a day or so I thought that it would be useful to have this posted.

This example uses source level metadata (JDK 5.0 annotation) to tag methods as cacheable and configure the refreshPeriod for a particular OSCache's cache group (it's the same concept as cache region when using OSCache with Hibernate).

We first tag the methods with annotations:
@Cacheable(modelId = "testCaching")
public List<Listing> getListings() {
// some implementation
}

@Cacheable(modelId = "testFlushing")
public void saveListings(List<Listing> listing) {
// some implementation
}

And then we add the following to the application-context.xml for Spring:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>
<!-- Domain -->
<bean id="listingRepositoryXmlImpl"
class="com.myco.service.ListingRepositoryXmlImpl"/>

<!-- Cache Setup -->
<bean id="autoproxy"
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />

<!--
The created cache manager is a singleton instance of
com.opensymphony.oscache.general.GeneralCacheAdministrator
-->
<bean id="cacheManager"
class="org.springmodules.cache.provider.oscache.OsCacheManagerFactoryBean">
<!-- Optional properties -->
<property name="configLocation"
value="classpath:oscache.properties" />
</bean>

<bean id="cacheProviderFacade"
class="org.springmodules.cache.provider.oscache.OsCacheFacade">
<property name="cacheManager" ref="cacheManager" />
</bean>

<!-- Cache-feature Weaving -->
<bean id="cachingAttributeSource"
class="org.springmodules.cache.annotations.AnnotationCachingAttributeSource">
</bean>

<bean id="cachingInterceptor"
class="org.springmodules.cache.interceptor.caching.MetadataCachingInterceptor">
<property name="cacheProviderFacade" ref="cacheProviderFacade" />
<property name="cachingAttributeSource"
ref="cachingAttributeSource" />
<property name="cachingListeners">
<list>
<!-- We don't have any listener for now
<ref bean="cachingListener" />
-->
</list>
</property>
<property name="cachingModels">
<props>
<!--
Bean properties are set here for the underlying CachingModel implementation (OsCacheCachingModel).
Here we are setting the "group" and "refreshPeriod".
Note that the PropertyEditor will translate the semicolon separated key-values into a java.util.Map
-->

<prop key="testCaching"><!-- Here we map the modelId "testCaching" (Spring) to group "testCache" (OSCache), we also set the refresh period -->
groups=testCache;refreshPeriod=5
</prop>
</props>
</property>
</bean>

<bean id="cachingAttributeSourceAdvisor"
class="org.springmodules.cache.interceptor.caching.CachingAttributeSourceAdvisor">
<constructor-arg ref="cachingInterceptor" />
</bean>

<bean id="flushingAttributeSource"
class="org.springmodules.cache.annotations.AnnotationFlushingAttributeSource">
</bean>

<bean id="flushingInterceptor"
class="org.springmodules.cache.interceptor.flush.MetadataFlushingInterceptor">
<property name="cacheProviderFacade" ref="cacheProviderFacade" />
<property name="flushingAttributeSource"
ref="flushingAttributeSource" />
<property name="flushingModels">
<props>
<!-- Refresh period is set here for the underlying CachingModel implementation (OsCacheCachingModel) -->

<prop key="testFlushing"><!-- Here we map the modelId "testFlushing" (Spring) to group "testCache" (OSCache) -->
groups=testCache
</prop>
</props>
</property>
</bean>

<bean id="flushingAttributeSourceAdvisor"
class="org.springmodules.cache.interceptor.flush.FlushingAttributeSourceAdvisor">
<constructor-arg ref="flushingInterceptor" />
</bean>

</beans>

And that's all there is to it. Happy caching!