흰 스타렉스에서 내가 내리지

외부 설정(환경변수) - @ConfigurationProperties, 그리고 검증 본문

Spring

외부 설정(환경변수) - @ConfigurationProperties, 그리고 검증

주씨. 2023. 11. 15. 15:20
728x90

외부 설정 묶음을 객체로 변환할 수 있다. 

이를 타입 안전한 설정 속성 이라 한다.

 

객체를 사용하면 타입을 사용할 수 있다. 

따라서 실수로 잘못된 타입이 들어오는 문제도 방지할 수 있고, 객체를 통해서 활용할 수 있는 부분들이 많아진다. 

 

# application.yml

my:
  datasource:
    url: local.db.com
    username: dabin
    password: heyDB
    etc:
      max-connection: 1
      timeout: 3400ms
      options: CACHE,ADMIN

 

 

# MyDataSourcePropertiesV1.java

@Data
@ConfigurationProperties("my.datasource")
public class MyDataSourcePropertiesV1 {
    private String url;
    private String username;
    private String password;
    private Etc etc = new Etc();

    @Data
    public static class Etc{
        private int maxConnection;
        private Duration timeout;
        private List<String> options = new ArrayList<>();
    }
}

 

- @ConfigurationProperties 는 외부 설정을 주입 받는 객체라는 뜻이다.
   여기에 외부 설정 KEY 의 묶음 시작점인 my.datasource를 적어준다.

   기본 주입 방식은 자바빈 프로퍼티 방식이기 때문에 Getter, Setter 가 필요하다. 

 

 

# MyDataSourceConfigV1.java

@Slf4j
@EnableConfigurationProperties(MyDataSourcePropertiesV1.class)
public class MyDataSourceConfigV1 {
    private final MyDataSourcePropertiesV1 properties;

    public MyDataSourceConfigV1(MyDataSourcePropertiesV1 properties) {
        this.properties = properties;
    }

    @Bean
    public MyDataSource dataSource(){
        return new MyDataSource(
                properties.getUrl(),
                properties.getUsername(),
                properties.getPassword(),
                properties.getEtc().getMaxConnection(),
                properties.getEtc().getTimeout(),
                properties.getEtc().getOptions()
        );
    }
}

 

- @EnableConfigurationProperties(MyDataSourcePropertiesV1.class)

      : 스프링에게 사용할 @CofigurationProperties 를 지정해준다.

 

 

@Import(MyDataSourceConfigV1.class)
@SpringBootApplication(scanBasePackages = "hello.datasource")
public class ExternalReadApplication {

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

}

 

 

 

 


# 타입안전

ConfigurationProperties 를 사용하면 타입 안전한 설정 속성을 사용할 수 있다. 

만약 maxConnection=a 로 입력하고 실행하면 에러가 발생한다.

 

타입이 다르면 오류가 발생하는 것이다. 

실수로 숫자를 입력하는 곳에 문자를 입력하는 문제를 방지해준다. 

따라서, ConfiigurationProperties 로 만든 외부 데이터는 타입에 대해서 믿고 사용할 수 있다. 

 

물론 @Value도 타입이 다르면 에러가 발생한다. 

 

 

# 정리

1. application.yml 에 필요한 외부 설정 추가

2. @ConfigurationProperties 를 통해서 MyDataSourcePropertiesV1 에 외부 설정 값 설정

3. 해당 값들을 읽어와서 MyDataSource 를 만들었음

 

 

++  표기법

스프링은 캐밥 표기법을 자바 낙타 표기법으로 중간에서 자동으로 변환해준다. 

- application.yml 에서는 max-connection, 

- Java 코드에서는 maxConnection

 

 


 

# @ConfigurationPropertiesScan

 

@ConfigurationProperties 를 하나하나 직접 등록할 때는 @EnableConfigurationProperties 를 사용한다. 

     → 예 : @EnableConfigurationProperties(MyDataSourcePropertiesV1.class)

 

@ConfigurationProperties 를 특정 범위로 자동 등록할 때는 @ConfigurationPropertiesScan 을 사용한다. 

@Slf4j
//@EnableConfigurationProperties(MyDataSourcePropertiesV1.class)
public class MyDataSourceConfigV1 { ... }

@EnableConfigurationProperties 를 주석 처리 한다. 안 쓸거임. 

즉, Config 에는 아무 어노테이션을 해줄 필요가 없다. 

 

이제 MainApplication으로 가서, @ConfigurationPropertiesScan 을 추가해 준다. 

// @Import(MyDataSourceConfigV1.class)
@SpringBootApplication(scanBasePackages = "hello.datasource")
@ConfigurationPropertiesScan
// @ConfigurationPropertiesScan({"com.example.app", "com.example.another"})
public class ExternalReadApplication { ... }

 

이는 빈을 직접 등록하는 것과 컴포넌트 스캔을 사용하는 차이와 비슷하다. 

 

 

 

근데 여기서 문제?

@Data 어노테이션으로 인해 Setter를 가지고 있기 때문에 코드 내에서 값이 바뀔 수 있다. 

외부 설정 값은 코드 내에서 값을 변경하면 안되는 것이다. 

이럴 때, Setter를 제거하고 대신에 생성자를 사용하면 중간에 데이터를 변경하는 실수를 근본적으로 방지할 수 있다. 

자세한 내용은 바로 아래부터.

 


# 외부설정 사용 - @ConfigurationProperties 생성자

@Data 어노테이션을 @Getter 로 바꾸고, 모든 필드를 포함하는 생성자를 만든다. 

그러면 값이 생성자를 통해 주입된다. 

"생성자를 만들어 두면, 생성자를 통해서 설정 정보를 주입한다."

@Getter
@ConfigurationProperties("my.datasource")
public class MyDataSourcePropertiesV2 {
    private String url;
    private String username;
    private String password;
    private Etc etc;

    public MyDataSourcePropertiesV2(String url, String username, String password, @DefaultValue Etc etc) {
        this.url = url;
        this.username = username;
        this.password = password;
        this.etc = etc;
    }

    @Getter
    public static class Etc{
        private int maxConnection;
        private Duration timeout;
        private List<String> options;

        public Etc(int maxConnection, Duration timeout, @DefaultValue("DEFAULT") List<String> options) {
            this.maxConnection = maxConnection;
            this.timeout = timeout;
            this.options = options;
        }
    }
}

 

@DefaultValue : 해당 값을 찾을 수 없는 경우 기본값을 사용한다. 

@DefaultValue Etc etc : etc 를 찾을 수 없는 경우 Etc 객체를 생성하고 내부에 들어가는 값은 비워둔다. (null, 0)

          → 위의 예제에서는, etc 하위 값이 존재하지 않을 경우 maxConnection 에는 0이, timeout에는 null 이 들어간다. 

@DefaultValue("DEFAULT") List<String> options : options 를 찾을 수 없을 경우 DEFAULT 라는 이름의 값을 사용한다. 리스트 안에 DEFAULT 만 들어가게 되는 것이다. 

그런데 @DefaultValue("CACHE,ADMIN") 이라고 한다고 두 가지 값이 배열에 들어가는 것이 아니고, 문자열 하나 전체가 첫번째 인덱스로 들어가게 된다. 

여러 개의 값을 집어 넣으려면, 다음과 같이 사용한다. 

@DefaultValue({"CACHE", "ADMIN"})

 

 

 

스프링 부트 3.0 이전에는 생성자 바인딩 시에 @ConstructorBinding 어노테이션을 필수로 사용해야 했다. 
스프링 부트 3.0 부터는 생성자가 하나일 때는 생략할 수 있다. 생성자가 둘 이상인 경우에는 사용할 생성자에 @ConstructorBinding 어노테이션을 적용하면 된다. 
@ConstructorBinding
public MyDataSourcePropertiesV2(...) { ... }

 

 

 

 

 

# 정리

1. application.yml 에 외부 설정 값 추가

2. @ConfigurationProperties 의 생성자 주입을 통해 값을 읽어들임. 객체 정의

3. @EnableConfiguraitonProperties 로 읽어들여 객체를 생성.

 

- Setter 가 없으므로 개발자가 중간에 실수로 값을 변경하는 문제가 발생하지 않음.

 

 

근데 여기서 또 문제?

숫자의 범위를 제한하고 싶다면?

max-connection은 0이 되면 안되잖아. 

max-connection을 최소 1로 설정하여, 1 미만의 수가 들어오면 앱 로딩 시점에 예외를 발생시키자. 

 


# 외부설정 사용 - @ConfigurationProperties 검증

@Validation 을 사용하면 되는데, 그거는 다음 글에서. 

'Spring' 카테고리의 다른 글

각 환경마다 서로 다른 빈 등록 - @Profile  (0) 2023.12.16
검증 - @Validation  (0) 2023.11.16
외부 설정(환경변수) - @Value  (0) 2023.11.15
설정 파일 프로필 별 분리  (0) 2023.11.08
@Conditional  (0) 2023.11.06