Annotated with @ConstructorBinding but defined as Spring component 해결

June Lee·2023년 8월 24일
0

Spring

목록 보기
9/9
@Configuration
@ConfigurationProperties("example")
public class RedisConfig {
	 @Setter
    private Map<String, String> property;

    private final ObjectMapper redisObjectMapper;

    public RedisConfig(ObjectMapper redisObjectMapper) {
        this.redisObjectMapper = redisObjectMapper;
    }
    ...
}

위와 같은 방식으로 사용하던 클래스에서 스프링 부트 3.XX(+자바 17)로 업데이트 이후 오류가 발생했다. (빨간 줄과 함께 에러는 뜨지만 돌아는가는 케이스..)

Annotated with @ConstructorBinding but defined as Spring component 

https://www.baeldung.com/configuration-properties-in-spring-boot#immutable-configurationproperties-binding
https://docs.spring.io/spring-boot/docs/2.4.x/reference/html/spring-boot-features.html#boot-features-external-config-constructor-binding

알아본 바를 정리하면,..

  • @ConfigurationProperties를 사용하면 기본적으로 ConstructorBinding을 사용하게 됨 (생성자를 이용한 바인딩)

  • @ConstructorBinding과 @Configuration이 같이 붙어있을 경우 다음과 같은 에러 발생
    : Annotated with @ConstructorBinding but defined as Spring component

-> 그 이유는, @Configuration이 붙어있는 경우, 스프링은 이것을 빈으로 관리(생성자, 필드 등을 통해 DI를 해줌)
그러나 @ConstructorBinding이 붙어있는 경우, 이 클래스는 생성자를 통해 외부 property를 바인딩해줘야 함
따라서 스프링이 이 클래스를 어떻게 다뤄야할지가 모호해짐

=> 따라서, @Configuration 등 스프링의 stereotype annotation이 붙은 클래스와 configuration property를 받아오기 위한 @ConfigurationProperties with @ConstructorBinding 어노테이션을 쓰는 클래스는 구분해줘야함 (그게 일반적인 사용 방법임)

수정된 코드는 아래와 같다.

@Configuration
@EnableConfigurationProperties(RedisConfig.RedisProperties.class)
public class RedisConfig {
    private final ObjectMapper redisObjectMapper;

    public RedisConfig(ObjectMapper redisObjectMapper) {
        this.redisObjectMapper = redisObjectMapper;
    }
    ...
    
    @Order(-1)
    @Bean("cacheRegionTemplateMap")
    public Map<String, ReactiveRedisTemplate<String, Object>> cacheRegionTemplateMap(RedisProperties redisProperties, DefaultListableBeanFactory factory, ClientResources clientResources) {
        Map<String, ReactiveRedisTemplate<String, Object>> cacheRegionTemplateMap = new HashMap<>();
        redisProperties.getController().keySet().forEach(region -> {
            ...
        });

        return cacheRegionTemplateMap;
    }
    
    ...
    
    @ConfigurationProperties("example")
    public static class RedisProperties {
        private Map<String, String> property = new HashMap<>();

        public RedisProperties(Map<String, String> property) {
            this.property = property;
        }

        public Map<String, String> getProperty() {
            return property;
        }
    }
}
    

@ConfigurationProperty의 코드 주석을 보면 아래와 같은 말이 있다.

Annotation for externalized configuration. Add this to a class definition or a @Bean method in a @Configuration class if you want to bind and validate some external Properties (e.g. from a .properties file).
Binding is either performed by calling setters on the annotated class or, if @ConstructorBinding is in use, by binding to the constructor parameters.

외부 설정을 위한 어노테이션. 외부 property를 가져오고 싶으면 클래스 생성시에 이걸 붙이거나 @Configuration 클래스의 @Bean 메소드에 붙여라.
바인딩은 어노테이션이 붙은 클래스의 setter를 통해 수행되거나, @ConstructorBinding이 붙어있으면 constructor를 이용해서 실행된다.

아마 팀원 분이 이 주석을 보고 @Configuration 클래스에 붙이면 된다는 생각이 들어서 붙이지 않았을까 하는 생각이 든다.

profile
📝 dev wiki

0개의 댓글