일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 스프링 폼
- PS
- exclusive lock
- BOJ
- eager
- shared lock
- 연관관계
- 이진탐색
- CHECK OPTION
- 연결리스트
- 유니크제약조건
- 동적sql
- fetch
- SQL프로그래밍
- 다대일
- 스토어드 프로시저
- 백트래킹
- querydsl
- dfs
- 지연로딩
- 낙관적락
- 즉시로딩
- 힙
- 비관적락
- 데코레이터
- 일대다
- JPQL
- 다대다
- FetchType
- execute
- Today
- Total
흰 스타렉스에서 내가 내리지
외부 설정(환경변수) - @ConfigurationProperties, 그리고 검증 본문
외부 설정 묶음을 객체로 변환할 수 있다.
이를 타입 안전한 설정 속성 이라 한다.
객체를 사용하면 타입을 사용할 수 있다.
따라서 실수로 잘못된 타입이 들어오는 문제도 방지할 수 있고, 객체를 통해서 활용할 수 있는 부분들이 많아진다.
# 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 |