SPRING/Spring Boot

[SpringBoot] 외부설정 가져오기(3) - @ConfigurationProperties

IT록흐 2023. 5. 7. 22:56
반응형

[SpringBoot] 외부설정 가져오기(2) - @Value

[SpringBoot] 외부설정 통합 - Enviroment Application은 외부에서 설정을 가져올 수 있다. 외부설정을 가져 오는 방법은 다양한데, SpringBoot는 최대한 통합하고 추상화하여, 외부설정을 간단히 조작할 수

lordofkangs.tistory.com

 
 

 
 
@Value 어노테이션은 설정값을 변수에 직접 넣는다. 
@Value에는 두 가지 제한점이 있다.
 
1) 변수마다 @Value를 선언해야한다.
설정이 필요한 모든 변수에 @Value를 선언하니 번거롭다.
 
2) @Value는 변수에 값을 주입하다보니 보안 및 가공이  어렵다.
 
외부설정을 데이터객체에 바로 주입하면 잘못된 데이터가 바인딩될 수 있다. 중간과정을 두어 유효성 검사를 하면 안전하게 데이터를 주입할 수 있다.
 
 

@ConfigurationProperties

 

 
 
@ConfigurationProperties는 외부설정을 담을 객체를 선언하는 어노테이션이다.
 

application.properties

my.datasource.url=my.db.com
my.datasource.username = my_user
my.datasource.password=my_pw
my.datasource.etc.timeout=4000ms

your.datasource.url=your.db.com
your.datasource.username=your_user
your.datasource.password=your_pw
your.datasource.etc.timeout=4500ms

 
 
@ConfigurationProperties는 외부설정을 Setter로 주입하는 자바빈프로퍼티 방식과 생성자로 주입하는 방식 2가지가 있다.
 
 

1) Setter 주입 방식

 
DataSourceProperties.java

@Slf4j
@Data
@ConfigurationProperties("my.datasource")
public class DataSourceProperties {

    private String url;
    private String username;
    private String password;
    private ETC etc = new ETC();

    @Data
    public class ETC{
        private Duration timeout;
    }

}

 
@Data 어노테이션은 @Getter,@Setter를 포함하는 어노테이션이다.
 
@ConfigurationProperties("my.datasource") 어노테이션은 DataSourceProperties 클래스가 설정정보를 담을 클래스임을 표시한다. "my.datasouce"로 설정의 범위를 정할 수 있다.
 
만약 depth가 깊어지는 경우, 클래스를 하나 생성하면 된다.  my.datasource.etc.timeout 설정은 연관객체로 구현된다.
 - private ETC etc = new ETC();
 
 

Datasource.java

@Slf4j
@RequiredArgsConstructor
@Data
public class DataSource {

    private final String url;
    private final String username;
    private final String password;
    private final Duration timeout;

    @PostConstruct
    public void init(){
        log.info("url={}", url);
        log.info("username={}",username);
        log.info("password={}", password);
        log.info("timeout={}",timeout);
    }
}

 
주입할 DataSource이다. 외부설정의 객체의 데이터를 해당 객체에 주입하려고 한다.
 
@PostConstruct 어노테이션은 데이터가 모두 주입되면 실행되는 메소드를 지정한다. init() 메소드는 주입받은 데이터를 로그로 찍는다. 그럼 DataSource를 Bean으로 생성하는 Config 파일을 생성해보자.
 
 
DataSourceConfig.java

@Slf4j
@Configuration
@EnableConfigurationProperties(DataSourceProperties.class) //중요!
@RequiredArgsConstructor
public class DataSourceConfig {

    public final DataSourceProperties dataSourceProperties;

    @Bean
    public DataSource myDataSource(){
        return new DataSource(dataSourceProperties.getUrl(),dataSourceProperties.getUsername(), dataSourceProperties.getPassword(), dataSourceProperties.getEtc().getTimeout());
    }
}

 
 
@EnableConfigurationProperties 어노테이션은 스프링에게 @ConfigurationProperties 파일을 등록하는 역할을 한다. 스프링은 DataSourceProperties를 Bean으로 등록하고 DataSourceConfig에 주입한다.
 
 
컴포넌트 스캔 -> DataSourceConfig의 @Configuration -> @EnableConfigurationProperties -> DataSourceProperties Bean 등록 -> DataSource 생성 
 
위 과정으로 처리된다.
그럼 스프링부트 프로젝트를 실행해보자.
 

 
DataSource가 생성되면서 찍은 로그이다.  depth가 1단계 많은 timeout도 잘 찍혔다. 
 
설정을 @ConfigurationProperties 어노테이션이 선언된 객체에 Setter를 이용하여 주입해보았다. 설정의 경우, 민감한 정보인 경우가 많다. 그래서 Setter를 열어두면 누군가가 설정을 임의로 수정할 가능성이 생긴다. 이런 경우를 원천차단하기 위해, 생성자 주입방식으로 객체를 생성할 수 있다.
 
 

2) 생성자 주입방식

 
 

DataSourceProperties.java

@Slf4j
@Getter // @Setter제거
@ConfigurationProperties("my.datasource")
public class DataSourceProperties {

    private String url;
    private String username;
    private String password;
    private ETC etc;

    public DataSourceProperties(@DefaultValue("No URL") 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{ // 중첨클래스, static으로 선언!
        private Duration timeout;
        public ETC(Duration timeout) {
            this.timeout = timeout;
        }
    }

}

 
@Setter를 제거하고 생성자를 만들어 설정을 생성자로 주입받는다. @DefaultValue를 사용하면 설정이 주입되지 않을 때, 디폴트값을 넣을 수 있다. 
 
그리고 Depth가 1 높은 ETC의 경우, static을 사용하여 중첩클래스로 선언한다. 외부클래스인 DataSourceProperties 이름으로 접근해야 설정주입이 되므로 static으로 선언해야 하는 것 같다. ( 정확한 이유는 모르겠다. ) static으로 선언하지 않으면 오류가 발생하니 주의 해야한다. 
 
설정 주입방식을 생성자주입방식으로 바꾼 후, SpringBoot 프로젝트를 실행해보자.
 

 
 
생성자 주입 방식으로 설정을 가져와 DataSource를 생성한 모습이다. 
 
이와같이,
@Value를 사용하지 않고 @ConfigurationProperties로 설정을 담는 그릇을 만들어 사용하면 더 안전하다. DataSource에 데이터가 들어가기 전에 미리 값의 타입이나 범위 등의 문제를 파악할 수 있다.
 
설정데이터의 범위를 지정해보자. 
 
자바 빈 검증기를 사용해야 한다. build.gradle에 아래설정을 추가한다.

implementation 'org.springframework.boot:spring-boot-starter-validation'

 
 
DataSourceProperties.java

@Slf4j
@Getter
@ConfigurationProperties("my.datasource")
@Validated
public class DataSourceProperties {

    @NotEmpty // 필수값
    private String url;
    @NotEmpty // 필수값
    private String username;
    private String password;
    private ETC etc;

    public DataSourceProperties(@DefaultValue("No URL") 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{
        @DurationMax(seconds = 60) // 최대 60초 허용
        private Duration timeout;
        public ETC(Duration timeout) {
            this.timeout = timeout;
        }
    }

}

 
@Validated 어노테이션으로 위 클래스의 데이터의 유효성검사 대상임을 표시한다.
 
@NotEmpty는 필수값이다. 반드시 값이 있어야 한다. 
@DurationMax는 시간범위의 최댓값을 설정한다. 60초를 설정해놓았다.
 
이와같이, @ConfigurationProperties 어노테이션을 활용하면
외부설정값이 프로그램 안으로 들어오기 전, 설정데이터의 유효성검사를 진행할 수 있다. 이로써 프로그램 로딩과정에서 사전에 문제를 발견할 수 있다.
 
 

application.properties

my.datasource.url=my.db.com
my.datasource.username = my_user
my.datasource.password =my_pw
my.datasource.etc.timeout=400000ms #60초 초과!

 
데이터 유효성에 벗어나는 외부설정을 만들어보았다. etc의 timeout은 60초를 초과한다.
 
그럼 프로그램을 실행해보자.
 

Description: Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'my.datasource' to com.example.demo.DataSourceProperties failed:

Property: my.datasource.etc.timeout
Value: "PT6M40S"
Origin: class path resource [application.properties] - 5:27
Reason: 다음 값보다 짧거나 같아야 합니다 60초

 
위와같이, 데이터를 바인딩하는 과정에서 오류가 발생한다. 외부설정값이 실제 데이터객체에 값이 들어가기 전에 오류를 발견하므로써 프로그램의 안정성을 높힐 수 있다.
 
 
 


 
 

참고자료

스프링 부트 - 핵심 원리와 활용 - 인프런 | 강의

실무에 필요한 스프링 부트는 이 강의 하나로 모두 정리해드립니다., - 강의 소개 | 인프런

www.inflearn.com

 

반응형