Статьи

Spring @Configuration и FactoryBean

Рассмотрим FactoryBean для определения кэша с помощью файла конфигурации Spring:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
<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>

Заводской компонент ConcurrentMapCacheFactoryBean — это компонент, который, в свою очередь, отвечает за создание компонента Cache.

Моя первая попытка перевести эту настройку в стиль @Configuration была следующей:

01
02
03
04
05
06
07
08
09
10
@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;
}

Однако это не сработало, причина в том, что здесь я обошел некоторые механизмы жизненного цикла Spring bean. Оказывается, что ConcurrentMapCacheFactoryBean также реализует интерфейс InitializingBean и активно инициализирует кэш в методе InitializingBean ‘afterPropertiesSet’. Теперь, напрямую вызывая factoryBean.getObject (), я полностью обошел метод afterPropertiesSet.

Есть два возможных решения:
1. Определите FactoryBean так же, как он определен в XML:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
@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;
}

В этом случае явный FactoryBean возвращается из метода @Bean, и Spring позаботится о вызове методов жизненного цикла этого компонента.

2. Реплицируйте поведение в соответствующих методах жизненного цикла, в этом конкретном случае я знаю, что FactoryBean создает экземпляр ConcurrentMapCache в методе afterPropertiesSet, я могу реплицировать это поведение непосредственно следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
@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;
}

О чем следует помнить при переводе FactoryBean из xml в @Configuration.

Замечания:
Рабочий одностраничный тест в виде гистеты доступен здесь:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package org.bk.samples.cache;
 
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
 
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
 
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={TestSpringCache.TestConfiguration.class})
public class TestSpringCache {
 
 @Autowired TestService testService;
 
 @Test
 public void testCache() {
  String response1 = testService.cachedMethod('param1', 'param2');
  String response2 = testService.cachedMethod('param1', 'param2');
  assertThat(response2, equalTo(response1));
 }
 
 
 @Configuration
 @EnableCaching
 @ComponentScan('org.bk.samples.cache')
 public static class TestConfiguration{
 
  @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;
  }
 }
 
}
 
interface TestService{
 String cachedMethod(String param1,String param2);
}
 
@Component
class TestServiceImpl implements TestService{
 
 @Cacheable(value='default', key='#p0.concat('-').concat(#p1)')
 public String cachedMethod(String param1, String param2){
  return 'response ' + new Random().nextInt();
 }
}

Ссылка: Spring @Configuration и FactoryBean от нашего партнера по JCG Биджу Кунджуммен в блоге « все и вся» .