@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 클래스에 붙이면 된다는 생각이 들어서 붙이지 않았을까 하는 생각이 든다.