티스토리 뷰
외부 설정 사용 - Environment
스프링은 Environment
를 활용해서 더 편리하게 외부 설정을 읽는 방법들을 제공한다.
Environment
@Value
- 값 주입@ConfigurationProperties
- 타입 안전한 설정 속성
아래 예제를 통해 외부 설정을 읽는 방법을 알아볼텐데, 예제 코드는 이해를 돕기 위함이므로 실제 DB에 접근하지는 않는다.
MyDataSource
@Slf4j
public class MyDataSource {
private String url;
private String username;
private String password;
private int maxConnection;
private Duration timeout;
private List<String> options;
public MyDataSource(String url, String username, String password, int maxConnection, Duration timeout, List<String> options) {
this.url = url;
this.username = username;
this.password = password;
this.maxConnection = maxConnection;
this.timeout = timeout;
this.options = options;
}
@PostConstruct
public void init() {
log.info("url={}", url);
log.info("username={}", username);
log.info("password={}", password);
log.info("maxConnection={}", maxConnection);
log.info("timeout={}", timeout);
log.info("options={}", options);
}
}
url
,username
,password
: 접속 url, 이름, 비밀번호maxConnection
: 최대 연결 수timeout
: 응답 지연시 타임아웃options
: 연결시 사용하는 기타 옵션들
application.properties
my.datasource.url=local.db.com
my.datasource.username=hyuuny
my.datasource.password=password
my.datasource.etc.max-connection=1
my.datasource.etc.timeout=3500ms
my.datasource.etc.options=CACHE,ADMIN
- 외부 속성은 설정 데이터(
appliation.properties
)를 사용한다. - 여기서는 별도의 프로필은 사용하지 않았다. 환경에 따라서 다른 설정값이 필요하다면, 각 환경에 맞는 프로필을 적용하면 된다.
이제 외부 속성을 읽어서 앞서 만든 MyDataSource
에 값을 설정하고 스프링 빈으로 등록해보자!
MyDataSourceEnvConfig
@Slf4j
@Configuration
public class MyDataSourceEnvConfig {
private final Environment env;
public MyDataSourceEnvConfig(Environment env) {
this.env = env;
}
@Bean
public MyDataSource myDataSource() {
String url = env.getProperty("my.datasource.url");
String username = env.getProperty("my.datasource.username");
String password = env.getProperty("my.datasource.password");
int maxConnection = env.getProperty("my.datasource.etc.max-connection", Integer.class);
Duration timeout = env.getProperty("my.datasource.etc.timeout", Duration.class);
List<String> options = env.getProperty("my.datasource.etc.options", List.class);
return new MyDataSource(url, username, password, maxConnection, timeout, options);
}
}
MyDataSource
를 스프링 빈으로 등록하는 자바 설정이다.Environment
를 사용하면 외부 설정의 종류와 관계없이 코드 안에서 일관성 있게 외부 설정을 조회할 수 있다.Environment.getProperty(key, Type)
를 호출할 때 타입 정보를 주면 해당 타입으로 변환해준다. (스프링 내부 변환기가 작동)env.getProperty("my.datasource.etc.max-connection", Integer.class)
: 문자 숫자로 변환env.getProperty("my.datasource.etc.timeout", Duration.class)
: 문자 Duration (기간) 변환env.getProperty("my.datasource.etc.options", List.class)
: List 변환 (A,B -> 문자 [A,B])
실행 결과
application.properties
에 필요한 외부 설정을 추가하고, Environment
를 통해서 해당 값들을 읽어서, MyDataSource
를 만들었다. 향후 외부 설정 방식이 달라져도, 예를 들어서 설정 데이터(application.properties
)를 사용하다가 커맨드 라인 옵션 인수나 자바 시스템 속성으로 변경해도 애플리케이션 코드를 그대로 유지할 수 있다.
하지만 이 방식의 단점은 Environment
를 직접 주입받고, env.getProperty(key)
를 통해서 값을 꺼내는 과정을 반복해야 한다는 점이다. 스프링은 @Value
를 통해서 외부 설정값을 주입 받는 더욱 편리한 기능을 제공한다.
외부설정 사용 - @Value
@Value
도 내부에서는 Environment
를 사용해서 값을 채운다.
@Slf4j
@Configuration
public class MyDataSourceValueConfig {
@Value("${my.datasource.url}")
private String url;
@Value("${my.datasource.username}")
private String username;
@Value("${my.datasource.password}")
private String password;
@Value("${my.datasource.etc.max-connection}")
private int maxConnection;
@Value("${my.datasource.etc.timeout}")
private Duration timeout;
@Value("${my.datasource.etc.options}")
private List<String> options;
@Bean
public MyDataSource myDataSource1() {
return new MyDataSource(url, username, password, maxConnection, timeout, options);
}
@Bean
public MyDataSource myDataSource2(
@Value("${my.datasource.url}") String url,
@Value("${my.datasource.username}") String username,
@Value("${my.datasource.password}") String password,
@Value("${my.datasource.etc.max-connection}") int maxConnection,
@Value("${my.datasource.etc.timeout}") Duration timeout,
@Value("${my.datasource.etc.options}") List<String> options
) {
return new MyDataSource(url, username, password, maxConnection, timeout, options);
}
}
@Value
에${}
를 사용해서 외부 설정의 키 값을 주면 원하는 값을 주입 받을 수 있다.@Value
는 필드에 사용할 수도 있고, 파라미터에 사용할 수도 있다.myDataSource1()
은 필드에 주입 받은 설정값을 사용한다.myDataSource2()
는 파라미터를 통해서 설정 값을 주입 받는다.
실행 결과
application.properties
에 필요한 외부 설정을 추가하고, @Value
를 통해서 해당 값들을 읽어서, MyDataSource
를 만들었다.
@Value
를 사용하는 방식도 좋지만, @Value
로 하나하나 외부 설정 정보의 키 값을 입력받고, 주입 받아와야 하는 부분이 번거롭다. 그리고 설정 데이터를 보면 하나하나 분리되어 있는 것이 아니라 정보의 묶음으로 되어 있다. 여기서는 my.datasource
부분으로 묶여있다. 이런 부분을 객체로 변환해서 사용할 수 있다면 더 편리하고 더 좋을 것 같다.
외부설정 사용 - @ConfigurationProperties
Type-safe Configuration Properties
스프링은 외부 설정의 묶음 정보를 객체로 변환하는 기능을 제공한다. 이것을 타입 안전한 설정 속성이라 한다.
객체를 사용하면 타입을 사용할 수 있다. 따라서 실수로 잘못된 타입이 들어오는 문제도 방지할 수 있고, 객체를 통해서 활용할 수 있는 부분들이 많아진다. 즉, 외부 설정을 자바 코드로 관리할 수 있는 것이다. 그리고 설정 정보 그 자체도 타입을 가지게 된다.
MyDataSourcePropertiesV1
@Setter
@Getter
@ConfigurationProperties("my.datasource")
public class MyDataSourcePropertiesV1 {
private String url;
private String username;
private String password;
private Etc etc;
@Setter
@Getter
public static class Etc {
private int maxConnection;
private Duration timeout;
private List<String> options = new ArrayList<>();
}
}
- 외부 설정을 주입 받을 객체를 생성한다. 그리고 각 필드를 외부 설정의 키 값에 맞추어 준비한다.
@ConfigurationProperties
이 있으면 외부 설정을 주입 받는 객체라는 뜻이다. 여기에 외부 설정 KEY의 묶음 시작점인my.datasource
를 적어준다.- 기본 주입 방식은 자바빈 프로퍼티 방식이므로,
Getter
,Setter
가 필요하다.
MyDataSourceConfigV1
@Slf4j
@EnableConfigurationProperties(MyDataSourcePropertiesV1.class)
public class MyDataSourceConfigV1 {
private final MyDataSourcePropertiesV1 properties;
public MyDataSourceConfigV1(MyDataSourcePropertiesV1 properties) {
this.properties = properties;
}
@Bean
public MyDataSource myDataSource() {
return new MyDataSource(
properties.getUrl(),
properties.getUsername(),
properties.getPassword(),
properties.getEtc().getMaxConnection(),
properties.getEtc().getTimeout(),
properties.getEtc().getOptions()
);
}
}
@EnableConfigurationProperties(MyDataSourcePropertiesV1.class)
- 스프링에게 사용할
@ConfigurationProperties
를 지정해주어야 한다. 이렇게 하면 해당 클래스는 스프링 빈으로 등록되고, 필요한 곳에서 주입 받아서 사용할 수 있다.
- 스프링에게 사용할
private final MyDataSourcePropertiesV1 properties
설정 속성을 생성자를 통해 주입 받아서 사용한다.
실행 결과
@ConfigurationPropertiesScan
@ConfigurationPropertiesScan({ "com.hyuuny.app", "com.hyuuny.another" })
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
@ConfigurationProperties
를 하나하나 직접 등록할 때는@EnableConfigurationProperties
를 사용한다.@ConfigurationProperties
를 특정 범위로 자동 등록할 때는@ConfigurationPropertiesScan
을 사용하면 된다.- 빈을 직접 등록하는 것과 컴포넌트 스캔을 사용하는 차이와 비슷하다.
MyDataSourcePropertiesV1
은 스프링 빈으로 등록된다. 그런데 Setter
를 가지고 있기 때문에 누군가 실수로 값을 변경하는 문제가 발생할 수 있다. 여기에 있는 값들은 외부 설정값을 사용해서 초기에만 설정되고, 이후에는 변경하면 안된다. 이럴 때 Setter
를 제거하고 대신에 생성자를 사용하면 중간에 데이터를 변경하는 실수를 근본적으로 방지할 수 있다.
대부분의 개발자가 MyDataSourcePropertiesV1
의 값은 변경하면 안된다고 인지하고 있지만, 어떤 개발자가 자신의 문제를 해결하기 위해 setter
를 통해서 값을 변경하게 되면, 애플리케이션 전체에 심각한 버그를 유발할 수 있다. 좋은 프로그램은 제약이 있는 프로그램이다.
외부설정 사용 - @ConfigurationProperties 생성자
@ConfigurationProperties
는 Getter, Setter를 사용하는 자바빈 프로퍼티 방식이 아니라 생성자를 통해서 객체를 만드는 기능도 지원한다.
MyDataSourcePropertiesV2
@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, 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;
}
}
}
- 생성자를 만들어 두면 생성자를 통해서 설정 정보를 주입한다.
@Getter
: 롬복이 자동으로 getter 를 만들어준다.@DefaultValue
: 해당 값을 찾을 수 없는 경우 기본값을 사용한다.@DefaultValue Etc etc
etc
를 찾을 수 없을 경우Etc
객체를 생성하고 내부에 들어가는 값은 비워둔다.(null, 0)
@DefaultValue("DEFAULT") List<String> options
options
를 찾을 수 없을 경우DEFAULT
라는 이름의 값을 사용한다.
실행 결과
application.properties
에 필요한 외부 설정을 추가하고, @ConfigurationProperties
의 생성자 주입을 통해서 값을 읽어들였다. Setter
가 없으므로 개발자가 중간에 실수로 값을 변경하는 문제가 발생하지 않는다.
타입과 객체를 통해서 숫자에 문자가 들어오는 것 같은 기본적인 타입 문제들은 해결이 되었다. 그런데 타입은 맞는데 숫자의 범위가 기대하는 것과 다르면 어떻게 될까? 예를 들어서 max-conneciton
의 값을 0 으로 설정하면 커넥션이 하나도 만들어지지 않는 심각한 문제가 발생한다고 가정해보자. max-conneciton
은 최소 1 이상으로 설정하지 않으면 애플리케이션 로딩 시점에 예외를 발생시켜서 빠르게 문제를 인지할 수 있도록 하고 싶다.
외부설정 사용 - @ConfigurationProperties 검증
@ConfigurationProperties
은 자바 객체이기 때문에 스프링이 자바 빈 검증기를 사용할 수 있도록 지원하고, 자바 빈 검증기를 사용하기 위해서는 spring-boot-starter-validation
이 필요하다.
implementation 'org.springframework.boot:spring-boot-starter-validation'
MyDataSourcePropertiesV3
@Getter
@Validated
@ConfigurationProperties("my.datasource")
public class MyDataSourcePropertiesV3 {
@NotEmpty
private String url;
@NotEmpty
private String username;
@NotEmpty
private String password;
private Etc etc;
public MyDataSourcePropertiesV3(String url, String username, String password, Etc etc) {
this.url = url;
this.username = username;
this.password = password;
this.etc = etc;
}
@Getter
public static class Etc {
@Min(1)
@Max(999)
private int maxConnection;
@DurationMin(seconds = 1)
@DurationMax(seconds = 60)
private Duration timeout;
private List<String> options;
public Etc(int maxConnection, Duration timeout, List<String> options) {
this.maxConnection = maxConnection;
this.timeout = timeout;
this.options = options;
}
}
}
@NotEmpty
url
,username
,password
는 항상 값이 있어야 한다. 필수 값이 된다.@Min(1) @Max(999) maxConnection
: 최소 1, 최대 999 의 값을 허용한다.@DurationMin(seconds = 1) @DurationMax(seconds = 60)
: 최소 1, 최대 60초를 허용한다.
MyDataSourceConfigV3
@Slf4j
@EnableConfigurationProperties(MyDataSourcePropertiesV3.class)
public class MyDataSourceConfigV3 {
private final MyDataSourcePropertiesV3 properties;
public MyDataSourceConfigV3(MyDataSourcePropertiesV3 properties) {
this.properties = properties;
}
@Bean
public MyDataSource myDataSource() {
return new MyDataSource(
properties.getUrl(),
properties.getUsername(),
properties.getPassword(),
properties.getEtc().getMaxConnection(),
properties.getEtc().getTimeout(),
properties.getEtc().getOptions()
);
}
}
my.datasource.url=local.db.com
my.datasource.username=hyuuny
my.datasource.password=password
my.datasource.etc.max-connection=0
my.datasource.etc.timeout=3500ms
my.datasource.etc.options=CACHE,ADMIN
my.datasource.etc.max-connection
의 값을 0으로 수정한다.
실행 결과
ConfigurationProperties
덕분에 타입 안전하고, 또 매우 편리하게 외부 설정을 사용할 수 있다. 그리고 검증기 덕분에 쉽고 편리하게 설정 정보를 검증할 수 있다. 가장 좋은 예외는 컴파일 예외, 그리고 애플리케이션 로딩 시점에 발생하는 예외이다. 가장 나쁜 예외는 사용자가 사용 중에 발생하는 런타임 예외이다.
ConfigurationProperties 장점
- 외부 설정을 객체로 편리하게 변환해서 사용할 수 있다.
- 외부 설정의 계층을 객체로 편리하게 표현할 수 있다.
- 외부 설정을 타입 안전하게 사용할 수 있다.
- 검증기를 적용할 수 있다.
Reference
김영한. 스프링 부트 - 핵심 원리와 활용. 인프런.
'Spring' 카테고리의 다른 글
[Spring] actuator (액츄에이터) (0) | 2023.04.10 |
---|---|
[Spring] YAML , @Profile (0) | 2023.04.09 |
[Spring] 외부설정과 프로필 2 (0) | 2023.04.03 |
[Spring] 외부설정과 프로필 1 (0) | 2023.04.02 |
[Spring] build.gradle 라이브러리 관리 (0) | 2023.03.13 |
- Total
- Today
- Yesterday
- 자료구조
- Real MySQL
- 알고리즘
- spring boot
- 데이터베이스
- 스프링
- 인프런
- Spring
- 리팩토링
- 노마드코더
- kotlin
- 노마드
- 김영한
- leetcode
- 구현
- 코테
- webflux
- 북클럽
- 정렬
- 문자열
- 코틀린
- 백준
- 스프링 부트
- 그리디
- MySQL
- Algorithm
- 릿코드
- mysql 8.0
- 스프링부트
- 파이썬
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |