@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초
위와같이, 데이터를 바인딩하는 과정에서 오류가 발생한다. 외부설정값이 실제 데이터객체에 값이 들어가기 전에 오류를 발견하므로써 프로그램의 안정성을 높힐 수 있다.
참고자료
'SPRING > Spring Boot' 카테고리의 다른 글
[SpringBoot] 엑츄에이터(Actuator)(2) - info 엔드포인트 (0) | 2023.05.11 |
---|---|
[SpringBoot] 엑츄에이터(Actuator)(1) - Actuator란? (0) | 2023.05.11 |
[SpringBoot] 외부설정 가져오기(2) - @Value (0) | 2023.05.07 |
[SpringBoot] Profile 사용하기 (0) | 2023.05.07 |
[SpringBoot] 설정파일(application.properties) (0) | 2023.05.05 |