Spring Boot - 외부 설정 파일과 프로필 관리
스프링 외부 설정 파일과 프로필 관리에 대해 자세히 알아보자
설정 파일
OS 환경 변수, 자바 시스템 속성, 커맨드 라인 옵션 인수는 사용해야 하는 값이 늘어날수록 사용하기가 불편해진다. 실무에서는 수십개의 설정값을 사용하기도 하므로 이런 값들을 프로그램을 실행할 때 마다 입력하게 되면 번거롭고, 관리도 어렵다.
그래서 나온 대안이 설정값을 파일에 넣어서 관리하는 방법이다. 그리고 애플리케이션 로딩 시점에 해당 파일을 읽어들이면 된다.
application.properties 또는 application.yml 라는 이름의 파일을 자바를 실행하는 위치에 만들어 두기만 하면 스프링이 해당 파일을 읽어서 사용할 수 있는 PropertySource 의 구현체를 제공한다.
하지만 각각의 서버에 각각 다른 설정 파일을 관리하는 것도 불편하지 않은가?
스프링은 이미 설정 데이터를 내부에 파일로 분리해 두고 외부 설정값(프로필)에 따라 각각 다른 파일을 읽는 방법을 다 구현해두었다.
프로필
프로필은 여러 환경에 맞춰 설정값을 자동으로 적용할 수 있도록 해주는 스프링의 기능이다.
spring.profiles.active 외부 설정에 값을 넣으면 해당 프로필을 사용한다고 판단한다.
예를들어
- 개발 환경: application-dev.properties
- 운영 환경: application-prod.properties
이런 식으로 설정 파일의 프로필을 정의했다면
- spring.profiles.active=dev -> dev 프로필이 활성화 되었다. application-dev.properties를 설정 데이터로 사용
- spring.profiles.active=prod -> prod 프로필이 활성화 되었다. application-prod.properties를 설정 데이터로 사용

실행 방법
IDE에서 커맨드 라인 옵션 인수 실행: --spring.profiles.active=dev
IDE에서 자바 시스템 속성 실행: -Dspring.profiles.active=dev
Jar 실행:
./gradlew clean build
build/libs 로 이동
java -Dspring.profiles.active=dev -jar external-0.0.1-SNAPSHOT.jar
java -jar external-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
그런데 파일을 두개로 분리하여 관리하는것 조차 불편하다고 생각한다면 하나로 관리하는 방법도 존재한다!
설정 파일 통합
스프링은 하나의 application.properties 파일 안에서 논리적으로 영역을 구분하는 방법을 제공한다.
- application.properties 구분 방법 : #--- 또는 !---
url=local.db.com
username=local_user
password=local_pw
#---
spring.config.activate.on-profile=dev
url=dev.db.com
username=dev_user
password=dev_pw
#---
spring.config.activate.on-profile=prod
url=prod.db.com
username=prod_user
password=prod_pw
- application.yml구분 방법 : ---
my:
datasource:
url: local.db.com
username: local_user
password: local_pw
---
spring:
config:
activate:
on-profile: dev
my:
datasource:
url: dev.db.com
username: dev_user
password: dev_pw
---
spring:
config:
activate:
on-profile: prod
my:
datasource:
url: prod.db.com
username: prod_user
password: prod_pw
- 단순하게 문서를 위에서 아래로 순서대로 읽으면서 값을 설정한다. 이때 기존 데이터가 있으면 덮어쓴다.
- 논리 문서에 spring.config.activate.on-profile 옵션이 있으면 해당 프로필을 사용할 때만 논리 문서를 적용한다.
- 프로필을 두 개 이상 사용하는 것도 가능하다.
- 프로필을 지정하지 않고 실행하면 기본값으로 맨 위의 기본값이 사용된다.
- 파일 분할 기호 위 아래엔 주석을 적으면 안된다는 점 유의!
외부 설정 조회 방법
스프링은 Environment 는 물론이고 Environment를 활용해서 더 편리하게 외부 설정을 읽는 방법들을 제공한다.
Environment
org.springframework.core.env.Environment
Environment를 통해서 특정 외부 설정에 종속되지 않고, 일관성 있게 key=value 형식의 외부 설정에 접근할 수 있다.
environment.getProperty(key) 를 통해서 값을 조회할 수 있다.
Environment는 내부에서 여러 과정을 거쳐서 PropertySource(각각의 외부 설정을 조회하는 구현체)들에 접근한다.
같은 값이 있을 경우를 대비해서 스프링은 미리 우선순위를 정해두었다.
우선순위 (높은 순)
-
@TestPropertySource (테스트에서 사용)
-
커맨드 라인 옵션 인수
- 자바 시스템 속성
-
OS 환경변수
-
설정 데이터( application.properties )
더 유연한 것, 범위가 더 좁은 것이 우선권을 가진다
@Value
@Value 애노테이션을 사용하면 외부 설정 값을 쉽게 주입받을 수 있다.
@Value 에 ${} 를 사용해서 외부설정의 키 값을 주면 원하는 값을 주입받을 수 있다.
@Value는 필드에 사용할 수도 있고, 파라미터에 사용할 수도 있다.
package hello.config;
import hello.datasource.MyDataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;
import java.util.List;
@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);
}
}
만약 키를 찾지 못할 경우 코드에서 기본값을 사용하려면 ':' 뒤에 기본값을 적어주면 된다.
@ConfigurationProperties
@ConfigurationProperties는 외부 설정 파일의 값을 특정 자바 객체에 주입하는 데 사용된다.
객체를 사용하면 타입을 사용할 수 있다. 따라서 실수로 잘못된 타입이 들어오는 문제도 방지할 수 있고, 객체를 통해서 활용할 수 있는 부분들이 많아진다. 쉽게 이야기해서 외부 설정을 자바 코드로 관리할 수 있는 것이다. 그리고 설정 정보 그 자체도 타입을 가지게 된다.
자바빈 프로퍼티 방식과 생성자를 통해 객체를 만들 수 있다.
자바빈 프로퍼티 방식은 Setter 를 가지고 있기 때문에 누군가 실수로 값을 변경하는 문제가 발생할 수 있기 때문에 생성자를 이용하는 방식만 설명하겠다.
package hello.datasource;
import lombok.Getter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
import java.time.Duration;
import java.util.List;
@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;
}
}
}
@ConfigurationProperties: 외부 설정을 주입 받는 객체라는 뜻이다. 여기에 외부 설정 KEY의 묶음 시작점인 my.datasource 를 적어준다.
@DefaultValue : 해당 값을 찾을 수 없는 경우 기본값을 사용한다.
빈 등록시에는 @EnableConfigurationProperties(MyDataSourcePropertiesV2.class)를 통해 스프링에게 사용할 @ConfigurationProperties를 지정해주어야 한다.
이렇게 하면 해당 클래스는 스프링 빈으로 등록되고, 필요한 곳에서 주입 받아서 사용할 수 있다.
설정 값 검증
@ConfigurationProperties를 통해 외부 설정 값이 올바른 타입인지는 검증이 가능하지만 숫자의 범위, 문자의 길이 같은 부분은 검증이 어렵다.
@ConfigurationProperties은 자바 객체이기 때문에 자바 빈 검증기를 사용할 수 있다.
spring-boot-starter-validation을 통해 사용할 수 있고
@Min, @Max 같은 애노테이션을 사용하여 값의 범위를 제한하거나 필수 값을 검증할 수 있다.
@Profile 사용
프로필과 외부 설정을 사용해서 각 환경마다 설정값을 다르게 적용하는 것 뿐만 아니라 각 환경마다 서로 다른 빈을 등록해야 한다면 어떻게 해야할까?
환경에 따라 서로 다른 스프링 빈을 등록해야 할 경우, @Profile 애노테이션을 사용할 수 있다.
특정 프로필이 활성화되었을 때에만 빈을 등록하도록 설정할 수 있으며, 이를 통해 개발 환경에서는 테스트용 빈, 운영 환경에서는 실제 기능을 수행하는 빈을 등록할 수 있다.
package hello.pay;
public interface PayClient {
void pay(int money);
}
package hello.pay;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class LocalPayClient implements PayClient {
@Override
public void pay(int money) {
log.info("로컬 결제 money={}", money);
}
}
package hello.pay;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ProdPayClient implements PayClient {
@Override
public void pay(int money) {
log.info("운영 결제 money={}", money);
}
}
이렇게 로컬 환경과 운영 환경을 구분하고
package hello.pay;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@Slf4j
@Configuration
public class PayConfig {
@Bean
@Profile("default")
public LocalPayClient localPayClient() {
log.info("LocalPayClient 빈 등록");
return new LocalPayClient();
}
@Bean
@Profile("prod")
public ProdPayClient prodPayClient() {
log.info("ProdPayClient 빈 등록");
return new ProdPayClient();
}
}
@Profile 애노테이션을 사용하면 해당 프로필이 활성화된 경우에만 빈을 등록한다.