Spring

[Spring Boot] @ConfigurationProperties 활성화시 @Profile 무시 에러

Joo.v7 2024. 11. 8. 20:22

1. 에러 발생

  • 의도
  • 문제
  • 문제 원인 파악
  • 문제가 발생한 코드

2. @ConfigurationProperties란?

  • Externalized Configuration (외부 구성)
  •  Externalized Configuration 사용 (바인딩)
  • @ConfigurationProperties

3. 해결

  • Bean 등록 방법 2가지
  • @Configuration + @Bean
  • @Component

4. 결론


1. 오류 발생

의도

  • Profile에 따라서 다른 Bean이 동작하게 하고 싶었다.
  • @Profile 기능을 이용해서 아래 코드처럼 eng Profile이 활성화되면 해당 클래스가 구현되게 하고 싶었다.
    (profile에 따라서 서로 다른 클래스 구현)

문제

  • @ConfigurationProperties("eng"), @Profile("eng"), @EnableConfigurationProperties(English.class)를 사용했는데 @Profile 에 따라서 Bean이 등록되는게 아니라, 모든 properties를 맵핑하는 클래스들이 다 실행되어서 Bean으로 등록됨.
  • 즉, @EnableConfigurationProperties가 @ConfigurationProperties를 활성화 시키는데 @Profile을 고려해서 활성화 후 Bean에 등록시켜야 하는데 이를 무시하고, 모든 @ConfigurationProperties가 구현된 클래스를 활성화시켜서 Bean에 등록함.

문제 원인 파악

  • @EnableConfigurationProperties로 @ConfigurationProperties 활성화 중, @Profile 어노테이션이 무시되고 있다.
  • profile 에 따라서 Bean이 등록되어야 하는데, 모든 Bean을 다 등록시킨다.

 문제가 발생한 코드

더보기
더보기
더보기

1. English.java

@Getter
@Setter
@Profile("eng")
@ConfigurationProperties("eng")
public class English implements OutputFormat{
    String city;
    String sector;
    String unitPrice;
    String billTotal;
}

 

2. resources/application-eng.properties

#csvParsing=true
#file.type=csv
#file.pricePath=price.csv
#file.accountPath=account.csv

jsonParsing=true
file.type=json
file.pricePath=price.json
file.accountPath=account.json

eng.city=city
eng.sector=sector
eng.unitPrice=unit price(won)
eng.billTotal=bill total(won)

 

3. @EnableConfigurationProperties로 @ConfigurationProperties 활성화

@EnableConfigurationProperties({CsvFileProperties.class, English.class, Korean.class})
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

 


2. @ConfigurationProperties란?

(1) Externalized Configuration (외부 구성)

  • Spring-boot는 같은 소스 코드로 여러 환경에서 동작할 수 있도록 외부화 설정을 제공한다
  • java properties, YAML, 환경변수, 실행 인자로 설정 가능하다.
  • 전체 프로젝트의 설정은 .properties, .yaml 중 하나만 사용하는 것을 권장하고, 동시에 존재하면 .properties가 우선한다.

(2) Externalized Configuration 사용 (바인딩)

  • Spring Boot는 설정값을 바인딩 하기 위한 2가지 방법을 제공.
    1. @Value 바인딩
    2. @ConfigurationProperties 바인딩

(3) @ConfigurationProperties

  • properties를 이 annotation으로 바인딩하여 사용.
  • 이 annotation으로 설정된 클래스Dependency Injection(의존성 주입)로 참조하여 사용
    (DI 3가지 방법: 생성자/필드/Setter 주입)

사용법

  • @ConfigurationProperties 클래스 선언
  • @ConfigurationProperties 활성화(2가지 방법)
  • 활성화가 되면서 Bean 등록됨. 
    1. @EnableConfigurationProperties("GreetingProperties.class")
      (활성화가 필요한 클래스를 직접 등록)
    2. @ConfigurationPropertiesScan
      (@ComponentScan과 비슷하게 패키지 단위로 스캔 -> 1번 방법이 너무 길어지는 경우 사용)
/* @ConfigurationProperties 클래스 선언 */
@Getter
@AllArgsConstructor // 외부에서 주입받는 거라서 DI 필요
@ConfigurationProperties("greeting") 
public class GreetingProperties {
    private String english;
    private String korean;
}


/* @ConfigurationProperties 활성화 */
@SpringBootApplication
@EnableConfigurationProperties(GreetingProperties.class)
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

 


3. 해결

정확한 이유는 모르겠지만 @EnableConfigurationProperties, @ConfigurationPropertiesScan ... 등을 사용해서 Bean 등록시 내부 로직에서 @Profile이 제대로 적용되지 않는 것 같다. 그런데 @Component나 @Configuration + @Bean을 활용하면 @Profile이 제대로 고려돼서 Bean이 등록된다.

Bean 등록 방법 2가지

  • Bean 등록 방법
    1. @Configuration + @Bean
    2. @Component

(1) @Configuration + @Bean

  • @ConfiguratonProperties를 선언 후
  • Bean 등록 과정에서 @Profile으로 활성화할 Bean이 선택됨.

코드

더보기
더보기
더보기

 1. English.java

@Getter
@Setter
@ConfigurationProperties("eng")
public class English implements OutputFormat{
    String city;
    String sector;
    String unitPrice;
    String billTotal;
}

 

2. FormatConfig.java

@Configuration
public class FormatConfig {

    @Bean
    @Profile("eng")
    public OutputFormat englishFormat() {
        return new English();
    }
    
    @Bean
    @Profile("!eng")
    public OutputFormat koreanFormat() {
        return new Korean();
    }
    
}

 

(2) @Component

  • @Component로 해당 클래스 Bean 등록.

코드

더보기
더보기
더보기

 English.java

@Getter
@Setter
@Component
@Profile("eng")
@ConfigurationProperties("eng")
public class English implements OutputFormat{
    String city;
    String sector;
    String unitPrice;
    String billTotal;
}

 


4. 결론

@ConfigurationProperties를 사용하고, 이를 활성화 시키기 위해서 @EnableConfigurationProperties ... 등을 무조건 사용해야 하는 줄 알았는데 아니었다.

 

내부 로직을 자세히 알 수 없지만 @ConfigurationProperties와 @Profile을 같이 사용 후 활성화를 위해 @EnableConfigurationProperties, @ConfigurationPropertiesScan을 사용하면 @Profile이 제대로 고려되지 않는다. 하지만 스캔을 하지 않고, Bean에 등록시키면 @Profile이 고려된다.

 


 

참고: https://docs.spring.io/spring-boot/api/java/org/springframework/boot/context/properties/ConfigurationProperties.html

 

ConfigurationProperties (Spring Boot 3.3.5 API)

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 o

docs.spring.io