부제: spring-integration 를 추가하면서 발행한 오류를 통한 ConversionService에 대한 추적
spring-boot 프로젝트에서 "org.springframework.boot:spring-boot-starter-web"를 사용하고 있었다.
엔드포인트로 TCP 연결이 필요해서 스프링 통합 프로젝트의 Spring Integration IP Support를 사용하기 위해 "org.springframework.integration:spring-integration-ip" 의존성을 추가하였다.
잘 돌아가는 애플리케이션이 시작이 되지 않는다. 메세지는 아래와 같다.
Description: Parameter 2 of constructor in com.tistory.repository.CacheSupplier required a single bean, but 2 were found:
- integrationConversionService: defined in null
- mvcConversionService: defined by method 'mvcConversionService' in class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class] Action: Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
CacheSupplier 라는 빈을 생성하기 위해 의존하고 있는 org.springframework.core.convert.ConversionService 을 주입받아야 하는데, 해당 인터페이스를 구현하는 빈이 2개 이상이라 정하기 어렵다라는 많이 보던 패턴이다.
위의 메세지처럼 @Qualifier 로 이름을 명시적으로 설정하거나, 빈에 @Primary로 우선순위로 정해주면 된다.
mvcConversionService
spring-boot-starter-web에 의존을 하면 기본으로 만들어주는 빈이다.
org.springframework.boot.autoconfigure.BackgroundPreinitializer 클래스의 performPreinitialization 메서드에는 ConversionServiceInitializer 를 수행하도록 되어 있다.
private void performPreinitialization() {
try {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
runSafely(new ConversionServiceInitializer());
runSafely(new ValidationInitializer());
runSafely(new MessageConverterInitializer());
runSafely(new MBeanFactoryInitializer());
runSafely(new JacksonInitializer());
runSafely(new CharsetInitializer());
preinitializationComplete.countDown();
}
// ...
}
하지만 mvcConversionService 라는 이름은 어디에 있는 걸까?
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration 설정 중에 EnableWebMvcConfiguration 라는 정적 중첩 클래스가 있는데 여기에 아래와 같은 코드가 있다.
@Bean
@Override
public FormattingConversionService mvcConversionService() {
WebConversionService conversionService = new WebConversionService(
this.mvcProperties.getDateFormat());
addFormatters(conversionService);
return conversionService;
}
바로 FormattingConversionService 가 ConversionService 인터페이스를 구현한 구현체인 것이다.
integrationConversionService
integrationConversionService 는 뭘까? 앞에 integration이 붙어서 알겠지만 spring integration 에서 사용하는 ConversionService로 보인다. 이 빈의 등록은 IntegrationRegistrar의 registerBeanDefinitions에서 수행하며 registerBuiltInBeans(registry); 실행 시 등록된다.
public class IntegrationRegistrar implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware {
private final static IntegrationConverterInitializer INTEGRATION_CONVERTER_INITIALIZER =
new IntegrationConverterInitializer();
private void registerBuiltInBeans(BeanDefinitionRegistry registry) {
// ...
if (!alreadyRegistered && !registriesProcessed.contains(registryId) && JacksonPresent.isJackson2Present()) {
registry.registerBeanDefinition(
IntegrationContextUtils.TO_STRING_FRIENDLY_JSON_NODE_TO_STRING_CONVERTER_BEAN_NAME,
BeanDefinitionBuilder.genericBeanDefinition(IntegrationConfigUtils.BASE_PACKAGE +
".json.ToStringFriendlyJsonNodeToStringConverter")
.getBeanDefinition());
INTEGRATION_CONVERTER_INITIALIZER.registerConverter(registry,
new RuntimeBeanReference(
IntegrationContextUtils.TO_STRING_FRIENDLY_JSON_NODE_TO_STRING_CONVERTER_BEAN_NAME));
}
registriesProcessed.add(registryId);
}
INTEGRATION_CONVERTER_INITIALIZER.registerConverter(registry, new RuntimeBeanReference( IntegrationContextUtils.TO_STRING_FRIENDLY_JSON_NODE_TO_STRING_CONVERTER_BEAN_NAME));
결국 IntegrationConverterInitializer#registerConverter 에 의해 수행되며 integrationConversionService 이름은 IntegrationUtils에 정의되어 있다.
public final class IntegrationUtils {
// ...
public static final String INTEGRATION_CONVERSION_SERVICE_BEAN_NAME = "integrationConversionService";
하지만 이 빈은 null이다.
해결책은?
stackoverflow에 같은 질문이 있다.
https://stackoverflow.com/questions/28111599/multiple-conversionservices-in-spring-boot
답변은 conversionService 를 직접 설정을 해주던지(타입에 의한 주입이 아닌 명시적인 이름에 의한 주입을 하게 수정하던지,
명시적으로 mvcConversionService으로 대상을 정해주던지 하면 된다.
@Bean(name="conversionService")
public ConversionService getConversionService() {
ConversionServiceFactoryBean bean = new ConversionServiceFactoryBean();
bean.afterPropertiesSet();
return bean.getObject();
}
// 또는
@Qualifier("mvcConversionService") ConversionService conversionService
프레임워크를 사용하다 문제가 발생시 해결을 위해서는 프레임워크의 내부의 구현에 대해 알 필요가 가끔 생긴다.
'Programing > Framework' 카테고리의 다른 글
[Spring] MockRestServiceServer를 이용한 RestTemplate 테스트 (0) | 2019.08.09 |
---|---|
[Spring] RestClientException 예외 정리 (0) | 2019.07.24 |
[스프링 부트] StringHttpMessageConverter 를 쓸 때 주의점 (0) | 2019.06.11 |
[spring boot] Type-safe Configuration Properties 쓸 때 주의점 (0) | 2019.06.11 |
[Spring] AOP로 로깅 코드 덜어내기 (0) | 2019.05.29 |