Quantcast
Channel: all and sundry
Viewing all articles
Browse latest Browse all 250

Spring @Configuration and FactoryBean

$
0
0
Consider a FactoryBean  for defining a cache using a Spring configuration file:

<cache:annotation-driven />
<context:component-scan base-package="org.bk.samples.cachexml"></context:component-scan>

<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<ref bean="defaultCache"/>
</set>
</property>
</bean>

<bean name="defaultCache" class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
<property name="name" value="default"/>
</bean>

The factory bean ConcurrentMapCacheFactoryBean is a bean which is in turn responsible for creating a Cache bean.

My first attempt at translating this setup to a @Configuration style was the following:


@Bean
public SimpleCacheManager cacheManager(){
SimpleCacheManager cacheManager = new SimpleCacheManager();
List<Cache> caches = new ArrayList<Cache>();
ConcurrentMapCacheFactoryBean cacheFactoryBean = new ConcurrentMapCacheFactoryBean();
cacheFactoryBean.setName("default");
caches.add(cacheFactoryBean.getObject());
cacheManager.setCaches(caches );
return cacheManager;
}

This did not work however, the reason is that here I have bypassed some Spring bean lifecycle mechanisms altogether. It turns out that ConcurrentMapCacheFactoryBean also implements the InitializingBean interface and does a eager initialization of the cache in the "afterPropertiesSet" method of InitializingBean. Now by directly calling factoryBean.getObject() , I was completely bypassing the afterPropertiesSet method.

There are two possible solutions:
1. Define the FactoryBean the same way it is defined in the XML:
@Bean
public SimpleCacheManager cacheManager(){
SimpleCacheManager cacheManager = new SimpleCacheManager();
List<Cache> caches = new ArrayList<Cache>();
caches.add(cacheBean().getObject());
cacheManager.setCaches(caches );
return cacheManager;
}

@Bean
public ConcurrentMapCacheFactoryBean cacheBean(){
ConcurrentMapCacheFactoryBean cacheFactoryBean = new ConcurrentMapCacheFactoryBean();
cacheFactoryBean.setName("default");
return cacheFactoryBean;
}
In this case, there is an explicit FactoryBean being returned from a @Bean method, and Spring will take care of calling the lifecycle methods on this bean.

2. Replicate the behavior in the relevant lifecycle methods, in this specific instance I know that the FactoryBean instantiates the ConcurrentMapCache in the afterPropertiesSet method, I can replicate this behavior directly this way:

@Bean
public SimpleCacheManager cacheManager(){
SimpleCacheManager cacheManager = new SimpleCacheManager();
List<Cache> caches = new ArrayList<Cache>();
caches.add(cacheBean());
cacheManager.setCaches(caches );
return cacheManager;
}

@Bean
public Cache cacheBean(){
Cache cache = new ConcurrentMapCache("default");
return cache;
}

Something to keep in mind when translating a FactoryBean from xml to @Configuration.

Note:
A working one page test as a gist is available here:

Viewing all articles
Browse latest Browse all 250

Trending Articles