인프런/스프링 부트 - 핵심 원리와 활용

8) 외부설정과 프로필 [2]

backend dev 2024. 11. 12.

외부 설정 - 커맨드 라인 옵션 인수

일반적인 커맨드 라인 인수

 

커맨드 라인에 전달하는 값은 형식이 없고, 단순히 띄어쓰기로 구분한다

 

 

 

커맨드 라인 옵션 인수(command line option arguments)

 

커맨드 라인 인수를 key=value 형식으로 구분하는 방법이 필요하다.

그래서 스프링에서는 커맨드 라인 인수를 key=value 형식으로 편리하게 사용할 수 있도록

스프링 만의 표준 방식을 정의했는데, 그것이 바로 커맨드 라인 옵션 인수이다

 

스프링은 커맨드 라인에 - (dash) 2개( -- )를 연결해서 시작하면 key=value 형식으로 정하고

이것을 커맨드 라인 옵션 인수라 한다.

 

--key=value 형식으로 사용한다.

--username=userA --username=userB 하나의 키에 여러 값도 지정할 수 있다.

 

 

CommandLineV1

@Slf4j
public class CommandLineV1 {

    public static void main(String[] args) {
        for (String arg : args) {
            System.out.println("arg = " + arg);
        }
    }
}

 

그냥 커맨드 라인 변수로 넣고 실행하면 아직도 키값으로 나눠주지않고 하나의 문자열로 인식한다.

 

 

CommandLineV2

@Slf4j
public class CommandLineV2 {

    // --url=devdb --username=dev_user --password=dev_pw mode=on
    public static void main(String[] args) {
        for (String arg : args) {
            System.out.println("arg = " + arg);
        }

        ApplicationArguments appArgs = new DefaultApplicationArguments(args);
        log.info("SourceArgs = {}", List.of(appArgs.getSourceArgs())); // 파싱안된 입력된 그대로 나온다
        log.info("NonOptionArgs = {}", appArgs.getNonOptionArgs()); // -- [대쉬.옵션]가 안들어간것만 나온다.
        log.info("OptionsNames = {}", appArgs.getOptionNames());// -- [대쉬,옵션]이 들어간것만 파싱해서 키값을 보여준다.

        Set<String> optionNames = appArgs.getOptionNames();
        for (String optionName : optionNames) {
            log.info("option arg {} = {}", 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);
        log.info("mode={}", mode);

    }
}

 

스프링이 제공하는 ApplicationArguments 인터페이스와 DefaultApplicationArguments 구현체를 사용하면

커맨드 라인 옵션 인수를 규격대로 파싱해서 편리하게 사용할 수 있다.

 

 

여기서 커맨드 라인 옵션 인수와, 옵션 인수가 아닌 것을 구분할 수 있다.

 

옵션 인수 -- 로 시작한다.

 

--url=devdb --username=dev_user --password=dev_pw

 

옵션 인수가 아님 -- 로 시작하지 않는다. 

mode=on

 

 

arg : 커맨드 라인의 입력 결과를 그대로 출력한다.

SourceArgs : 커맨드 라인 인수 전부를 출력한다.

NonOptionArgs = [mode=on] : 옵션 인수가 아니다. key=value 형식으로 파싱되지 않는다. 
-- 를 앞에 사용하지 않았다.

OptionNames = [password, url, username] : key=value 형식으로 사용되는 옵션 인수다.
-- 를 앞에 사용했다.

url , username , password 는 옵션 인수이므로 appArgs.getOptionValues(key)로 
조회할 수 있다.

mode 는 옵션 인수가 아니므로 appArgs.getOptionValues(key) 로 조회할 수 없다. 
따라서 결과는 null 이다.

 

 

참고

 

참고로 옵션 인수는 

--username=userA --username=userB

처럼 하나의 키에 여러 값을 포함할 수 있기 때문에 appArgs.getOptionValues(key) 의 결과는 리스트( List )를 반환한다.

커맨드 라인 옵션 인수는 자바 언어의 표준 기능이 아니다.

스프링이 편리함을 위해 제공하는 기능이다.

 

 

 

 

외부 설정 - 커맨드 라인 옵션 인수와 스프링 부트

 

스프링 부트는 커맨드 라인을 포함해서 커맨드 라인 옵션 인수를 활용할 수 있는

ApplicationArguments를 스프링 빈으로 등록해둔다.

 

그리고 그 안에 입력한 커맨드 라인을 저장해둔다.

그래서 해당 빈을 주입 받으면 커맨드 라인으 로 입력한 값을 어디서든 사용할 수 있다

 

CommandLineBean - src/main 하위

@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));
        }

    }
}

 

@PostConstruct라서 객체 생성후, 의존성 주입 완료 후 실행된다.

[ 서버가 온전히 실행 된 후가 아님 , 서버가 온전히 다 실행된후에 진행된 로그는 prop 이부분 ]

 

 

 

외부 설정 - 스프링 통합

지금까지 살펴본, 커맨드 라인 옵션 인수, 자바 시스템 속성, OS 환경변수

모두 외부 설정을 key=value 형식으로 사용할 수 있는 방법이다.

 

그런데 이 외부 설정값을 읽어서 사용하는 개발자 입장에서 단순하게 생각해보면,

모두 key=value 형식이고, 설정값을 외부로 뽑아둔 것이다.

 

그런데 어디에 있는 외부 설정값을 읽어야 하는지에 따라서 각각 읽는 방법이 다르다는 단점이 있다.

 

예를 들어서 OS 환경 변수에 두면 System.getenv(key) 를 사용해야 하고,

자바 시스템 속성을 사용하면 System.getProperty(key) 를 사용해야 한다.

만약 OS에 환경 변수를 두었는데, 이후에 정책이 변경되어서 자바 시스템 속성에 환경 변수를 두기로 했다고 가정해보자. 그러면 해당 코드들을 모두 변경해야 한다.

 

외부 설정값이 어디에 위치하든 상관없이 일관성 있고, 편리하게 key=value 형식의 외부 설정값을 읽을 수 있으면

사용하는 개발자 입장에서 더 편리하고 또 외부 설정값을 설정하는 방법도 더 유연해질 수 있다.

예를 들어서 외부 설정 값을 OS 환경변수를 사용하다가

자바 시스템 속성으로 변경하는 경우에 소스코드를 다시 빌드하지 않고 그대로 사용할 수 있다.

 

스프링은 이 문제를 Environment 와 PropertySource 라는 추상화를 통해서 해결한다

 

 

 

스프링의 외부 설정 통합

PropertySource

 

org.springframework.core.env.PropertySource
스프링은 PropertySource 라는 추상 클래스를 제공하고, 각각의 외부 설정를 조회하는
XxxPropertySource 구현체를 만들어두었다.

예)
CommandLinePropertySource
SystemEnvironmentPropertySource
스프링은 로딩 시점에 필요한 PropertySource 들을 생성하고,
Environment 에서 사용할 수 있게 연결해둔다.

 

 

Environment

org.springframework.core.env.Environment
Environment 를 통해서 특정 외부 설정에 종속되지 않고, 
일관성 있게 key=value 형식의 외부 설정에접근할 수 있다.

environment.getProperty(key) 를 통해서 값을 조회할 수 있다.
Environment 는 내부에서 여러 과정을 거쳐서 PropertySource 들에 접근한다.

같은 값이 있을 경우를 대비해서 스프링은 미리 우선순위를 정해두었다. (뒤에서 설명한다.)

모든 외부 설정은 이제 Environment 를 통해서 조회하면 된다.

 

 

설정 데이터(파일)

 

여기에 우리가 잘 아는 application.properties , application.yml 도 PropertySource에 추가된다.

따라서 Environment 를 통해서 접근할 수 있다.

 

 

EnvironmentCheck

@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);

    }
}

 

커맨드 라인 옵션 인수 실행

--url=devdb --username=dev_user --password=dev_pw

 

자바 시스템 속성 실행

-Durl=devdb -Dusername=dev_user -Dpassword=dev_pw

 

 

다른 외부설정 방식이지만 Environment를 이용하면 같은 결과가 나온다. 

 

커맨드 라인 옵션 인수, 자바 시스템 속성 모두 Environment를 통해서 동일한 방법으로 읽을 수 있는 것을 확인했다.

 

스프링은 Environment 를 통해서 외부 설정을 읽는 방법을 추상화했다.

 

덕분에 자바 시스템 속성을 사용하다가 만약 커맨드 라인 옵션 인수를 사용하도록 읽는 방법이 변경되어도,

개발 소스 코드는 전혀 변경하지 않아도 된다.

 

 

우선순위

예를 들어서 커맨드 라인 옵션 인수와 자바 시스템 속성을 다음과 같이 중복해서 설정하면 어떻게 될까?

 

커맨드 라인 옵션 인수 실행
--url=proddb --username=prod_user --password=prod_pw

자바 시스템 속성 실행
-Durl=devdb -Dusername=dev_user -Dpassword=dev_pw

 

우선순위는 상식 선에서 딱 2가지만 기억하면 된다.

 

더 유연한 것이 우선권을 가진다.

(변경하기 어려운 파일 보다 실행시 원하는 값을 줄 수 있는 자바 시스템 속성이 더 우선권을 가진다.)

 

 

범위가 넒은 것 보다 좁은 것이 우선권을 가진다.

(자바 시스템 속성은 해당 JVM 안에서 모두 접근할 수 있 다.

반면에 커맨드 라인 옵션 인수는 main 의 arg를 통해서 들어오기 때문에 접근 범위가 더 좁다.)

 

 

자바 시스템 속성과 커맨드 라인 옵션 인수의 경우 커맨드 라인 옵션 인수의 범위가 더 좁기 때문에

커맨드 라인 옵션 인 수가 우선권을 가진다

 

 

 

 

설정 데이터1 - 외부 파일

지금까지 학습한

OS 환경 변수, 자바 시스템 속성, 커맨드 라인 옵션 인수는 사용해야 하는 값이 늘어날 수 록 사용하기가 불편해진다.

 

실무에서는 수십개의 설정값을 사용하기도 하므로

이런 값들을 프로그램을 실행할 때 마다 입력하게 되면 번거롭고, 관리도 어렵다.

 

그래서 등장하는 대안으로는 설정값을 파일에 넣어서 관리하는 방법이다.

 

그리고 애플리케이션 로딩 시점에 해당 파일을 읽어들이면 된다.

그 중에서도 .properties 라는 파일은 key=value 형식을 사용해서 설정값을 관리하기에 아주 적합하다.

 

application.properties 개발 서버에 있는 외부 파일

url=dev.db.com
username=dev_user
password=dev_pw

 

 

application.properties 운영 서버에 있는 외부 파일

url=prod.db.com
username=prod_user
password=prod_pw

 

예를 들면 개발 서버와 운영 서버 각각에 application.properties 라는 같은 이름의 파일을 준비해둔다.

그리고 애플리케이션 로딩 시점에 해당 파일을 읽어서 그 속에 있는 값들을 외부 설정값으로 사용하면 된다.

참고로 파일 이름이 같으므로 애플리케이션 코드는 그대로 유지할 수 있다.

 

 

 

스프링과 설정 데이터

 

개발자가 파일을 읽어서 설정값으로 사용할 수 있도록 개발을 해야겠지만,

스프링 부트는 이미 이런 부분을 다 구현해두 었다.

 

개발자는 application.properties 라는 이름의 파일을 자바를 실행하는 위치에 만들어 두기만 하면 된다.

그러면 스프링이 해당 파일을 읽어서 사용할 수 있는 PropertySource 의 구현체를 제공한다.

 

스프링에서는 이러한 application.properties 파일을 설정 데이터(Config data)라 한다.

당연히 설정 데이터도 Environment 를 통해서 조회할 수 있다.

 

 

 

참고

지금부터 설명할 내용은 application.properties 대신에 yml 형식의 application.yml 에도 동일하게 적용된다.

yml 과 application.yml 은 뒤에 자세히 설명한다.

 

 

 

동작 확인

 

./gradlew clean

build build/libs 로 이동

해당 위치에 application.properties 파일 생성

 

 

java -jar external-0.0.1-SNAPSHOT.jar 실행

username이 윈도우계정이름으로 나오는 이유는 윈도우 환경변수의 우선순위가 더 높아서이다.

[  username 이라는 윈도우 환경변수가 존재함 ] 

 

이렇게 각각의 환경에 따라 설정 파일의 내용을 다르게 준비하면 된다.

덕분에 설정값의 내용이 많고 복잡해도 파일로 편리하게 관리할 수 있다.

 

 

남은 문제

외부 설정을 별도의 파일로 관리하게 되면 설정 파일 자체를 관리하기 번거로운 문제가 발생한다.

 

서버가 10대면 변경사항이 있을 때 10대 서버의 설정 파일을 모두 각각 변경해야 하는 불편함이 있다.

 

설정 파일이 별도로 관리되기 때문에 설정값의 변경 이력을 확인하기 어렵다.

 

특히 설정값의 변경 이력이 프로젝트 코드들과 어떻게 영향을 주고 받는지 그 이력을 같이 확인하기 어렵다.

 

 

 

 

설정 데이터2 - 내부 파일 분리

설정 파일을 외부에 관리하는 것은 상당히 번거로운 일이다.

 

설정을 변경할 때 마다 서버에 들어가서 각각의 변경 사항을 수정해두어야 한다.

(물론 이것을 자동화 하기 위해 노력할 수는 있다)

 

이 문제를 해결하는 간단한 방법은 설정 파일을 프로젝트 내부에 포함해서 관리하는 것이다.

 

그리고 빌드 시점에 함께 빌드되게 하는 것이다.

이렇게 하면 애플리케이션을 배포할 때 설정 파일의 변경 사항도 함께 배포할 수 있다.

쉽게 이야기해서 jar 하나로 설정 데이터까지 포함해서 관리하는 것이다.

 

 

0. 프로젝트 안에 소스 코드 뿐만 아니라 각 환경에 필요한 설정 데이터도 함께 포함해서 관리한다.

개발용 설정 파일: application-dev.properties

운영용 설정 파일: application-prod.properties

 

1. 빌드 시점에 개발, 운영 설정 파일을 모두 포함해서 빌드한다.

 

2. app.jar 는 개발, 운영 두 설정 파일을 모두 가지고 배포된다.

 

3. 실행할 때 어떤 설정 데이터를 읽어야 할지 최소한의 구분은 필요하다.

개발 환경이라면 application-dev.properties 를 읽어야 하고,
운영 환경이라면 application-prod.properties 를 읽어야 한다.

실행할 때 외부 설정을 사용해서 개발 서버는 dev 라는 값을 제공하고, 
운영 서버는 prod 라는 값을 제공하자. 편의상 이 값을 프로필이라 하자.

dev 프로필이 넘어오면 application-dev.properties 를 읽어서 사용한다.
prod 프로필이 넘어오면 application-prod.properties 를 읽어서 사용한다.

 

 

외부 설정으로 넘어온 프로필 값이 dev 라면 application-dev.properties 를 읽고

prod 라면 application-prod.properties 를 읽어서 사용하면 된다

 

스프링은 이미 설정 데이터를 내부에 파일로 분리해 두고 외부 설정값(프로필)에 따라

각각 다른 파일을 읽는 방법을 다 구현해두었다.

 

 

스프링과 내부 설정 파일 읽기

main/resources 에 다음 파일을 추가하자

 

application-dev.properties // 개발 프로필에서 사용 properties
url=dev.db.com
username=dev_user
password=dev_pw 

application-prod.properties // 운영 프로필에서 사용 properties
url=prod.db.com
username=prod_user
password=prod_pw

 

 

 

프로필

스프링은 이런 곳에서 사용하기 위해 프로필이라는 개념을 지원한다.

 

spring.profiles.active 외부 설정에 값을 넣으면 해당 프로필을 사용한다고 판단한다.

application.properties 예시

그리고 프로필에 따라서 다음과 같은 규칙으로 해당 프로필에 맞는 내부 파일(설정 데이터)을 조회한다

application-{profile}.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
더보기

-Dspring.profiles.active=dev

 
java -Dspring.profiles.active=dev -jar external-0.0.1-SNAPSHOT.jar
  • 여기서 -Dspring.profiles.active=dev는 JVM 시스템 속성을 통해 프로파일을 설정하는 방법입니다.
  • 이 경우, spring.profiles.active 설정이 JVM 수준에서 지정되므로, 애플리케이션에서 접근할 수 있습니다.
  • JVM 옵션으로 설정된 값은 애플리케이션 내에서 변경할 수 없으며, 모든 스프링 설정 파일에서 공통으로 참조됩니다.

2. --spring.profiles.active=dev

java -jar external-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
  • 여기서 --spring.profiles.active=dev는 커맨드라인 인수로 스프링 부트 애플리케이션에 직접 전달하는 방법입니다.
  • 이 경우, spring.profiles.active는 스프링 부트 애플리케이션 컨텍스트 내부에서만 유효하며, 시스템 속성으로 설정되지는 않습니다.
  • 스프링 부트 애플리케이션이 시작될 때 해당 프로파일만을 활성화합니다.

차이점 요약

  • -Dspring.profiles.active=dev: JVM 수준 시스템 속성으로 설정됩니다. JVM 내의 모든 스프링 관련 설정에서 공통으로 참조됩니다.
  • --spring.profiles.active=dev: 스프링 부트 애플리케이션의 커맨드라인 인수로 전달됩니다. 애플리케이션 컨텍스트 내에서만 유효합니다

 

커맨드 라인 옵션 방식, 자바 시스템 속성 방식으로 원하는 프로필을 활성화 할 수 있다

 

 

 

설정 데이터3 - 내부 파일 합체

설정 파일을 각각 분리해서 관리하면 한눈에 전체가 들어오지 않는 단점이 있다.

 

스프링은 이런 단점을 보완하기 위해 물리적인 하나의 파일 안에서 논리적으로 영역을 구분하는 방법을 제공한다.

 

 

 

 

기존에는 dev 환경은 application-dev.properties , prod 환경은 applicationprod.properties 파일이 필요했다.

 

스프링은 하나의 application.properties 파일 안에서 논리적으로 영역을 구분하는 방법을 제공한다.

 

application.properties 라는 하나의 파일 안에서 논리적으로 영역을 나눌 수 있다.

 

application.properties 구분 방법 #--- 또는 !--- (dash 3)

application.yml 구분 방법 --- (dash 3)

 

그림의 오른쪽 application.properties는 하나의 파일이지만 내부에 2개의 논리 문서로 구분되어 있다

 

dev 프로필이 활성화 되면 상위 설정 데이터가 사용된다.

prod 프로필이 활성화 되면 하위 설정 데이터가 사용된다.

 

프로필에 따라 논리적으로 구분된 설정 데이터를 활성화 하는 방법

-> spring.config.activate.on-profile 에 프로필 값 지정

 

 

설정 데이터를 하나의 파일로 통합하기

우선 기존 내용을 사용하지 않도록 정리해야 한다.

다음 내용은 사용하지 않도록 # 을 사용해서 주석 처리하자.

-> application-dev.properties 주석 처리

-> application-prod.properties 주석 처리

 

 

 

application.properties

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

 

속성 파일 구분 기호에는 선행 공백이 없어야 하며 정확히 3개의 하이픈 문자가 있어야 한다.

 

구분 기호 바로 앞과 뒤의 줄은 같은 주석 접두사가 아니어야 한다.

 

파일을 분할하는 #--- 주석 위 아래는 주석을 적으면 안된다.

 

#
#---

분할 기호 위에 주석이 있는 예시 ->  문서가 정상적으로 읽히지 않을 수 있다.

 

#---
#

 

분할 기호 아래에 주석이 있는 예시 -> 문서가 정상적으로 읽히지 않을 수 있다.

 

 

 

 

우선순위 - 설정 데이터

 

application.properties

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

 

이런 상태에서 만약 프로필을 적용하지 않는다면 어떻게 될까?

--spring.profiles.active=dev 이런 옵션을 지정하지 않는다는 뜻이다.

프로필을 적용하지 않고 실행하면 해당하는 프로필이 없으므로 키를 각각 조회하면 값은 null 이 된다.

 

실행 결과를 보면 첫줄에 활성 프로필이 없어서 default 라는 이름의 프로필이 활성화 되는 것을 확인할 수 있다.

프로필을 지정하지 않고 실행하면 스프링은 기본으로 default 라는 이름의 프로필을 사용한다.

 

 

기본값

내 PC에서 개발하는 것을 보통 로컬( local ) 개발 환경이라 한다.

이때도 항상 프로필을 지정하면서 실행하는 것은 상당히 피곤할 것이다.

설정 데이터에는 기본값을 지정할 수 있는데, 프로필 지정과 무관하게 이 값은 항상 사용된다.

 

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

 

 

스프링은 문서를 위에서 아래로 순서대로 읽으면서 설정한다.

 

여기서 처음에 나오는 다음 논리 문서는 spring.config.activate.on-profile 와 같은 프로필 정보가 없다.

 

따라서 프로필과 무관하게 설정 데이터를 읽어서 사용한다.

 

이렇게 프로필 지정과 무관하게 사용되는 것을 기본값이라 한다


위쪽 설정에는 특정 프로필 지정이 없으므로, 특정 프로필 지정없이 실행됬을경우 적용된다. 

[ 사실 스프링 동작 방식상 어떤 프로필이 와도 제일 위의 코드가 프로필 지정없이 작성되었기에 이것은 무조건 실행된다. ]

 

 

사실 스프링은 단순하게 문서를 위에서 아래로 순서대로 읽으면서 사용할 값을 설정한다.

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

 

1. 스프링은 순서상 위에 있는 local 관련 논리 문서의 데이터들을 읽어서 설정한다.

여기에는 spring.config.activate.on-profile 와 같은 별도의 프로필을 지정하지 않았기 때문에

프로필과 무관하게 항상 값을 사용하도록 설정한다

 

어떤 프로필이 적용되더라도 아래처럼 일단 값이 설정된다. 

url=local.db.com
username=local_user
password=local_pw

 

2. 스프링은 그 다음 순서로 dev 관련 논리 문서를 읽는데 만약 dev 프로필이 설정되어있다면

기존 데이터를 dev 관련 논리 문서의 값으로 대체한다.

물론 dev 프로필을 사용하지 않는다면 dev 관련 논리 문서는 무시되고, 그 값도 사용하지 않는다

 

dev 프로필이 적용되어있다면 값이 치환된다.

url=local.db.com -> dev.db.com
username=local_user -> dev_user
password=local_pw -> dev_pw

 

3. 스프링은 그 다음 순서로 prod 관련 논리 문서를 읽는데 만약 prod 프로필이 설정되어있다면

기존 데이터를 prod 관련 논리 문서의 값으로 대체한다.

물론 prod 프로필을 사용하지 않는다면 prod 관련 논리 문서는 무시되고, 그 값도 사용하지 않는다.

 

만약 dev,prod 두개의 프로필이 지정되었다면 다음 순서인 prod로 치환된다.
[ 스프링은 우선순의에 따라 마지막에 지정된 프로필 설정을 적용하기 때문이다.

url=dev.db.com -> prod.db.com
username=dev_user -> prod_user
password=dev_pw -> prod_pw

 

참고로 프로필을 한번에 둘 이상 설정하는 것도 가능하다.

--spring.profiles.active=dev,prod

 

 

 

순서대로 설정 확인

극단적인 예시를 통해서 순서를 확실히 이해해보자.

 

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
#---
url=hello.db.com

 

스프링이 설정 파일을 위에서 아래로 순서대로 읽어서 사용할 값을 설정한다는 것은 이 예제를 실행해보면 확실히 이해 할 수 있다.

 

1. 스프링은 처음에 local 관련 논리 문서의 데이터들을 읽어서 설정한다.

여기에는 별도의 프로필을 지정하지 않았기 때문에 프로필과 무관하게 항상 값이 설정된다.

 

2. 스프링은 그 다음 순서로 dev 관련 논리 문서를 읽는데 만약 dev 프로필이 설정되어있다면 기존 데이터를 dev 관련 논리 문서의 값으로 대체한다.

 

3. 스프링은 그 다음 순서로 prod 관련 논리 문서를 읽는데 만약 prod 프로필이 설정되어있다면 기존 데이터를 prod 관련 논리 문서의 값으로 대체한다.

 

4. 스프링은 마지막으로 hello 관련 논리 문서의 데이터들을 읽어서 설정한다.

여기에는 별도의 프로 필을 지정하지 않았기 때문에 프로필과 무관하게 항상 값이 설정된다.

 

 

위에서 아래로 순서대로 실행하는데, 마지막에는 프로필이 없기 때문에 항상 마지막의 값들을 적용하게 된다.

만약 prod 프로필을 사용한다면 다음과 같이 설정된다.

username은 윈도우 계정명으로 적용된 모습

 

 

물론 이렇게 사용하는 것은 의미가 없다.

이해를 돕기 위해 이렇게 극단적인 예제를 사용했다.

보통은 기본값을 처음에 두고 그 다음에 프로필이 필요한 논리 문서들을 둔다.

 

 

정리

 

단순하게 문서를 위에서 아래로 순서대로 읽으면서 값을 설정한다. 이때 기존 데이터가 있으면 덮어쓴다.

논리 문서에 spring.config.activate.on-profile 옵션이 있으면 해당 프로필을 사용할 때만 논리 문서를 적용한다.

 

 

 

 

 

우선순위 - 전체

 

스프링 부트는 같은 애플리케이션 코드를 유지하면서 다양한 외부 설정을 사용할 수 있도록 지원한다.

 

 

외부 설정에 대한 우선순위 - 스프링 공식 문서

https://docs.spring.io/spring-boot/reference/features/external-config.html#features.external-config

 

 

우선순위는 위에서 아래로 적용된다. 아래가 더 우선순위가 높다.

 

 

 

자주 사용하는 우선순위

[ 밑에것이 더 우선순위가 높다. ]

 

- 설정 데이터( application.properties )

- OS 환경변수

- 자바 시스템 속성

- 커맨드 라인 옵션 인수

- @TestPropertySource (테스트에서 사용)

 

 

설정 데이터 우선순위

jar 내부 application.properties
jar 내부 프로필 적용 파일 application-{profile}.properties
jar 외부 application.properties
jar 외부 프로필 적용 파일 application-{profile}.properties

 

 

우선순위 이해 방법

우선순위는 상식 선에서 딱 2가지만 생각하면 된다

 

 

1. 더 유연한 것이 우선권을 가진다.

(변경하기 어려운 파일 보다 실행시 원하는 값을 줄 수 있는 자바 시스템 속성이 더 우선권을 가진다.)

 

2. 범위가 넒은 것 보다 좁은 것이 우선권을 가진다.

OS 환경변수 보다 자바 시스템 속성이 우선권이 있다.

자바 시스템 속성 보다 커맨드 라인 옵션 인수가 우선권이 있다

 

 

추가 또는 변경되는 방식

 

Environment 를 통해서 조회하는 관점에서 보면 외부 설정값들은 계속 추가되거나 기존 값을 덮어서 변경하는 것 처럼 보인다.

물론 실제 값을 덮어서 변경하는 것은 아니고, 우선순위가 높은 값이 조회되는 것이다.

그런데 이렇게 이해하면 개념적으로 더 쉽게 이해할 수 있다.

 

 

예를 들어서

 

설정 데이터( application.properties )에 다음과 같이 설정했다.

application.properties

url=local.db.com

 

 

자바 시스템 속성을 다음과 같이 적용했다.

 

자바 시스템 속성 추가

-Dusername=local_user

 

 

 

조회 결과

url=local.db.com
username=local_user

 

자바 시스템 속성에서 기존에 없던 키 값을 추가했기 때문에 속성이 추가되었다.

 

 

커맨드 라인 옵션 인수를 다음과 같이 적용했다.

커맨드 라인 옵션 인수 추가
--url=dev.db.com

 

 

 

조회 결과

url=dev.db.com
username=local_user

커맨드 라인 옵션 인수는 기존에 있던 url 이라는 키 값을 사용했기 때문에 기존에 있던 값이 새로운 값으로 변경되었다

[ 커맨드 라인 옵션 인수가 우선순위가 설정데이터 [application.properties] 보다 높기때문에 url 값을 덮어버린다.

 

 

정리

이렇게 우선순위에 따라서 설정을 추가하거나 변경하는 방식은 상당히 편리하면서도 유연한 구조를 만들어준다.

실무에서 대부분의 개발자들은 applicaiton.properties에 외부 설정값들을 보관한다.

 

이렇게 설정 데이터를 기본으로 사용하다가 일부 속성을 변경할 필요가 있다면

더 높은 우선순위를 가지는 자바 시스템 속성이나 커맨드 라인 옵션 인 를 사용하면 되는 것이다.

또는 기본적으로 application.properties 를 jar 내부에 내장하고 있다가,

특별한 환경에서는 application.properties를 외부 파일로 새로 만들고 변경하고 싶은 일부 속성만 입력해서

변경하는 것도 가능 하다.

 

 

 

 

댓글