springboot-external라는 프로젝트를 새로 생성하였습니다.
하나의 애플리케이션을 여러 다른 환경에서 사용해야 할 때가 있다.
문제는 각각의 환경에 따라서 서로 다른 설정값이 존재한다는 점이다. 예를 들어서 애플리케이션이 개발DB
에 접근하려면 dev.db.com
이라는 url 정보가 필요한데, 운영DB에 접근하려면 prod.db.com
이라는
서로 다른 url을 사용해야 한다.
이렇게 하면 각각의 환경에 맞는 개발app.jar
, 운영app.jar
가 만들어지므로 해당 파일들을 각 환경별로
배포하면 된다.
단점
실행 시점에 외부 설정값 주입
app.jar
를 빌드한다. 이 안에는 설정값을 두지 않는다.app.jar
를 실행할 때 dev.db.com
값을 외부 설정으로 주입한다.app.jar
를 실행할 때 prod.db.com
값을 외부 설정으로 주입한다.유지보수하기 좋은 애플리케이션 개발의 가장 기본 원칙은 변하는 것과 변하지 않는 것을 분리하는 것이다.
외부 설정의 일반적인 방법은 4가지가 있다.
main(args)
메서드에서 사용url=dev.db.com
url=prod.db.com
OS 환경 변수(OS environment variables)는 해당 OS를 사용하는 모든 프로그램에서 읽을 수 있는 설정값이다.
조회 방법
윈도우 OS: set
애플리케이션에서 OS 환경 변수의 값 읽어보기.
OsEnv
package hello.external;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
@Slf4j
public class OsEnv {
public static void main(String[] args) {
Map<String, String> envMap = System.getenv();
for (String key : envMap.keySet()) {
log.info("env {}={}", key, System.getenv(key));
}
}
}
System.getenv()
를 사용하면 전체 OS 환경 변수를 Map
으로 조회할 수 있다.System.getenv(key)
를 사용하면 특정 OS 환경 변수의 값을 String
으로 조회할 수 있다.테스트 결과
System.getenv()
를 사용해서 외부 설정을 사용 할 수 있다.System.getenv("DBURL")
로 조회해서 읽는다.DBURL=dev.db.com
DBURL=prod.db.com
자바 시스템 속성(Java System properties)은 실행한 JVM 안에서 접근 가능한 외부 설정이다. 추가로 자바가 내부에서 미리 설정해두고 사용하는 속성들도 있다.
자바 시스템 속성은 다음과 같이 자바 프로그램을 실행할 때 사용
java -Durl=dev -jar app.jar
-D
VM 옵션을 통해서 key=value
형식을 주면 된다. 이 예제는 url=dev
속성이 추가된다.-D
옵션이 - jar
보다 앞에 있다.JavaSystemProperties
package hello.external;
import lombok.extern.slf4j.Slf4j;
import java.util.Properties;
@Slf4j
public class JavaSystemProperties {
public static void main(String[] args) {
Properties properties = System.getProperties();
for(Object key : properties.keySet()){
log.info("prop {}={}", key, System.getProperty(String.valueOf(key)));
}
}
}
System.getProperties()
를 사용하면 Map
과 유사한(Map
의 자식 타입) key=value
형식의Properties
를 받을 수 있다. (모든 자바 시스템 속성 조회)System.getProperty(key)
를 사용하면 속성값을 조회할 수 있다.테스트 결과
사용자가 직접 정의하는 자바 시스템 속성을 추가(url
, username
, password
를 조회)
String url = System.getProperty("url");
String username = System.getProperty("username");
String password = System.getProperty("password");
log.info("url={}", url);
log.info("username={}", username);
log.info("password={}", password);
IDE에서 실행시 VM 옵션 추가
-Durl=devdb -Dusername=dev_user -Dpassword=dev_pw
테스트 결과
커맨드 라인 인수(Command line arguments)는 애플리케이션 실행 시점에 외부 설정값을 main(args)
메서드의 args
파라미터로 전달하는 방법이다.
다음과 같이 사용한다.
java -jar app.jar dataA dataB
dataA
, dataB
2개의args
에 전달된다.package hello.external;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CommandLineV1 {
public static void main(String[] args) {
for (String arg : args) {
log.info("arg {}", arg);
}
}
}
IDE에서 실행시 커맨드 라인 인수 추가
테스트 결과
key=value 형식 입력
url=devdb username=dev_user password=dev_pw
테스트 결과
=
을 기준으로 직접 데이터를 파싱해서 key=value
형식에 맞도록 분리해야 한다.일반적인 커맨드 라인 인수(단순히 띄어쓰기로 구분)
aaa bbb
-> [aaa, bbb]
값 2개hello world
-> [hello, world]
값 2개"hello world"
-> [hello world]
(공백을 연결하려면 " 를 사용하면 된다.) 값 1개key=value
-> [key=value]
값 1개커맨드 라인 옵션 인수(command line option arguments)
스프링은 커맨드 라인에 -
(dash) 2개(--
)를 연결해서 시작하면 key=value
형식으로 정하고 이것을 커맨드 라인 옵션 인수라 한다.
--key=value
형식으로 사용한다.--username=userA --username=userB
하나의 키에 여러 값도 지정할 수 있다.CommandLineV2
package hello.external;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.DefaultApplicationArguments;
import java.util.List;
import java.util.Set;
@Slf4j
public class CommandLineV2 {
public static void main(String[] args) {
for (String arg : args){
log.info("arg ={}", arg);
}
ApplicationArguments appArgs = new DefaultApplicationArguments(args);
log.info("SourceArgs = {}", List.of(appArgs.getSourceArgs()));
log.info("NonOptionArgs = {}", appArgs.getNonOptionArgs());
log.info("OptionNames = {}", appArgs.getOptionNames());
Set<String> optionNames = appArgs.getOptionNames();
for (String optionName : optionNames) {
log.info("option args {}={}", optionName, appArgs.getOptionValues(optionName));
}
List<String> url = appArgs.getOptionValues("url");
List<String> username = appArgs.getOptionValues("username");
List<String> password = appArgs.getOptionValues("password");
List<String> mode = appArgs.getOptionValues("mode");
log.info("url={}", url);
log.info("username={}", username);
log.info("password={}", password);
// mode는 옵션인수 (--)가 아니기 때문에 나오지 않음
log.info("mode={}", mode);
}
}
스프링이 제공하는 ApplicationArguments
인터페이스와 DefaultApplicationArguments
구현체를
사용하면 커맨드 라인 옵션 인수를 규격대로 파싱해서 편리하게 사용할 수 있다.
옵션 인수
--
로 시작한다.
--url=devdb
--username=dev_user
--password=dev_pw
테스트 결과
스프링 부트는 커맨드 라인을 포함해서 커맨드 라인 옵션 인수를 활용할 수 있는 ApplicationArguments
를 스프링 빈으로 등록해둔다.
CommandLineBean
package hello;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Set;
@Slf4j
@Component
public class CommandLineBean {
private final ApplicationArguments arguments;
public CommandLineBean(ApplicationArguments arguments) {
this.arguments = arguments;
}
@PostConstruct
public void init() {
log.info("source {}", List.of(arguments.getSourceArgs()));
log.info("optionNames {}", arguments.getOptionNames());
Set<String> optionNames = arguments.getOptionNames();
for (String optionName : optionNames) {
log.info("option args {}={}", optionName, arguments.getOptionValues(optionName));
}
}
}
인수 설정
애플리케이션 실행 결과
key=value
형식의 외부 설정값을 읽을 수 있으면 사용하는 개발자 입장에서 더 편리하고 또 외부 설정값을 설정하는 방법도 더 유연해질 수 있다.Environment
와 PropertySource
라는 추상화를 통해서 해결한다.스프링의 외부 설정 통합
PropertySource
PropertySource
라는 추상 클래스를 제공하고, 각각의 외부 설정를 조회하는 XxxPropertySource
구현체를 만들어두었다.PropertySource
들을 생성하고, Environment
에서 사용할 수 있게 연결해둔다.Environment
Environment
를 통해서 특정 외부 설정에 종속되지 않고, 일관성 있게 key=value
형식의 외부 설정에 접근할 수 있다.environment.getProperty(key)
를 통해서 값을 조회할 수 있다.Environment
는 내부에서 여러 과정을 거쳐서 PropertySource
들에 접근한다.Environment
를 통해서 조회하면 된다.설정 데이터(파일)
여기에 우리가 잘 아는 application.properties
, application.yml
도 PropertySource
에 추가된다. 따라서 Environment
를 통해서 접근할 수 있다.
EnvironmentCheck
package hello;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class EnvironmentCheck {
private final Environment env;
public EnvironmentCheck(Environment env) {
this.env = env;
}
@PostConstruct
public void init() {
String url = env.getProperty("url");
String username = env.getProperty("username");
String password = env.getProperty("password");
log.info("env url={}", url);
log.info("env username={}", username);
log.info("env password={}", password);
}
}
테스트 결과
스프링은 Environment
를 통해서 외부 설정을 읽는 방법을 추상화했다. 덕분에 자바 시스템 속성을 사용하다가 만약 커맨드 라인 옵션 인수를 사용하도록 읽는 방법이 변경되어도, 개발 소스 코드는 전혀 변경하지 않아도 된다.
.properteis
라는 파일은 key=vlaue
형식을 사용해서 설정값을 관리하기에 아주 적합예를 들면 개발 서버와 운영 서버 각각에 application.properties 라는 같은 이름의 파일을 준비해둔다. 그리고 애플리케이션 로딩 시점에 해당 파일을 읽어서 그 속에 있는 값들을 외부 설정값으로 사용하면 된다.
스프링과 설정 데이터
PropertySource
의 구현체를 제공한다. 스프링에서는 이러한 application.properties
파일을 설정 데이터(Config data)라 한다.동작 확인
./gradlew clean build
build/libs
로 이동application.properties
파일 생성java -jar external-0.0.1-SNAPSHOT.jar
실행이렇게 각각의 환경에 따라 설정 파일의 내용을 다르게 준비하면 된다. 덕분에 설정값의 내용이 많고 복잡해 도 파일로 편리하게 관리할 수 있다.
application-dev.properties
application-prod.properties
app.jar
는 개발, 운영 두 설정 파일을 모두 가지고 배포된다.스프링은 이미 설정 데이터를 내부에 파일로 분리해두고 외부 설정값(프로필)에 따라 각각 다른 파일을 읽는 방법을 다 구현해두었다.
스프링과 내부 설정 파일 읽기
main/resources
에 다음 파일을 추가
application-dev.properties
url=dev.db.com
username=dev_user
password=dev_pw
application-prod.properties
url=dev.db.com
username=dev_user
password=dev_pw
프로필
spring.profiles.active
외부 설정에 값을 넣으면 해당 프로필을 사용한다고 판단한다.spring.profiles.active=dev
dev
프로필이 활성화 되었다.application-dev.properties
를 설정 데이터로 사용한다.spring.profiles.active=prod
prod
프로필이 활성화 되었다.application-prod.properties
를 설정 데이터로 사용한다.application.properties
라는 하나의 파일 안에서 논리적으로 영역을 나눌 수 있다.application.properties
구분 방법 #---
또는 !---
(dash 3)application.yml
구분 방법 ---
(dash 3)설정 데이터를 하나의 파일로 통합하기
앞서 썼던 properties는 주석 처리 했다.
application.properties
spring.config.activate.on-profile=dev
my_url=dev.db.com
my_username=dev_user
my_password=dev_pw
#---
spring.config.activate.on-profile=prod
my_url=prod.db.com
my_username=prod_user
my_password=prod_pw
prod 설정 후 애플리케이션 실행
이제 application.properties
라는 파일 하나에 통합해서 다양한 프로필의 설정 데이터를 관리할 수 있다.
application.properties
에서 프로필을 적용하지 않으면 우선순위는 어떻게 될까?null
이 된다.
실행 결과를 보면 첫줄에 활성 프로필이 없어서 default
라는 이름의 프로필이 활성화 되는 것을 확인할 수 있다. 프로필을 지정하지 않고 실행하면 스프링은 기본으로 default
라는 이름의 프로필을 사용한다.
application.properties - 수정
my_url=local.db.com
my_username=local_user
my_password=local_pw
프로필과 무관하게 설정 데이터를 읽어서 사용한다. 이렇게 프로필 지정과 무관하게 사용되는 것을 기본값이라 한다.
스프링은 단순하게 문서를 위에서 아래로 순서대로 읽으면서 사용할 값을 설정한다.
예시
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
#---
url=hello.db.com
만약 prod
프로필을 사용한다면?
The following 1 profile is active: "prod"
...
url=hello.db.com
username=prod_user
password=prod_pw
마지막에 url이 있기 때문에 hello.db.com
이 설정된다.
외부 설정에 대한 우선순위 - 스프링 공식 문서
https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config
우선순위는 위에서 아래로 적용된다. 아래가 더 우선순위가 높다.
자주 사용하는 우선순위
application.properties
)@TestPropertySource
(테스트에서 사용)설정 데이터 우선순위
application.properties
application-{profile}.properties
application.properties
application-{profile}.properties