외부 설정이 필요한 이유
애플리케이션을 개발하다 보면 운영 환경과 개발 환경이 서로 다른 설정값을 요구하는 경우가 많다.
- 개발 환경: 개발 서버, 개발 DB 사용
- 운영 환경: 운영 서버, 운영 DB 사용
각각의 환경에 따라서 서로 다른 설정값이 존재한다.
예를 들어 DB 접속 URL만 해도 환경에 따라 다를 수 있다.
- 개발: dev.db.com
- 운영: prod.db.com
잘못된 접근 방식: 환경별로 빌드 결과물을 나누는 것
초기에는각각의 환경에 맞게 애플리케이션을 빌드하는 것이다.
개발 환경용 app-dev.jar, 운영 환경용 app-prod.jar 등 환경마다 서로 다른 빌드 결과물 생성
환경에 따라 변하는 설정값을 내부에 포함

개발 환경에는 dev.db.com 이 필요하므로 이 값을 애플리케이션 코드에 넣은 다음에 빌드해서 개발 app.jar 를 만든다.
운영 환경에는 prod.db.com 이 필요하므로 이 값을 애플리케이션 코드에 넣은 다음에 빌드해서 운영 app.jar 를 만든다.
이렇게 하면 각각의 환경에 맞는 개발app.jar , 운영app.jar 가 만들어지므로 해당 파일들을 각 환경별로 배포하면 된다.
환경에 따라 변하는 설정값을 내부에 포함시 단점
- 매 환경마다 별도로 빌드해야 하므로 비효율적이다.
- 개발 버전과 운영버전의 빌드 결과물이 다르다.
- 따라서 개발 환경에서 검증이 되더라고 운영환경 다른 빌드 결과를 사용하기 때문에 예상치 못한 문제가 발생할 수 있다. 개발용 빌드가 끝나고 검증한 다음에 운영용 빌드를 해야 하는데 그 사이에 누군가 다른 코드를 변경할 수도 있다. 한마디로 진짜 같은 소스코드에서 나온 결과물인지 검증하기 어렵다.
- 각 환경에 맞추어 최종 빌드가 되어 나온 결과물을 다른 환경에서 사용할 수 없어서 유연성이 떨어진다.
- 향후 다른 환경이 필요하면 그 곳에 맞도록 또 빌드해야 한다.
올바른 접근: 빌드는 한 번만, 설정은 실행 시 주입
그래서 보통 빌드는 한번만 하고 각 환경에 맞추어 실행 시점에 외부 설정값을 주입한다.
환경에 따라 변하는 설정값을 실행 시점에 주입

배포 환경과 무관하게 하나의 빌드 결과물을 만든다.
- 단일 빌드로 하나의 app.jar 만 생성
- 설정값은 실행시점에 각 환경에 따라 외부에서 주입한다.
# 개발 서버
java -Durl=dev.db.com -jar app.jar
# 운영 서버
java -Durl=prod.db.com -jar app.jar
환경에 따라 변하는 설정값을 실행 시점에 주입 - 장점
- 빌드는 한 번만 하고, 어떤 환경에서도 동일한 jar 를 사용할 수 있다.
- 운영 환경 추가도 설정값만 주입하면 되므로 매우 유연하다.
- 무엇보다 개발에서 충분히 테스트한 검증된 결과물 그대로 운영에 배포할 수 있어 안정성이 높다.
유지보수하기 좋은 애플리케이션 개발의 가장 기본 원칙은 변하는 것과 변하지 않는 것을 분리하는 것이다.
외부 설정을 주입하는 4가지 방법
애플리케이션을 실행할 때 필요한 설정값을 외부에서 어떻게 불러와서 애플리케이션에 전달할 수 있을까?

외부 설정은 일반적으로 다음 4가지 방법이 있다.
- OS 환경 변수: OS 에서 지원하는 외부 설정, 해당 OS 를 사용하는 프로세스에서 사용
- 자바 시스템 속성: 자바에서 지원하는 외부 설정, 해당 JVM 안에서 사용
- 자바 커맨드 라인 인수: 커맨드 라인에서 전달하는 외부 설정, 실행시 main(args) 메서드에서 사용
- 외부 파일(설정 데이터) : 프로그램에서 외부 파일을 직접 일억서 사용
- 애플리케이션에서 특정 위치에 파일을 읽도록 해준다. 예) data/hello.txt
- 그리고 각 서버마다 해당 파일안에 다른 설정 정보를 남겨둔다.
- 개발 서버 hello.txt : url=dev.db.com
- 운영 서버 hello.txt : url= prod.db.com
외부 설정 - OS 환경 변수 사용 예
OS 환경 변수 (OS environment variables) 는 해당 OS 를 사용하는 모든 프로그램에서 읽을 수 있는 설정값이다.
한마디로 다른 외부 설정과 비교해서 사용범위가 가장 넓다.
조회 방법
윈도우 OS: set
MAC, 리눅스 OS: printenv
- set 실행결과

애플리케이션에서 OS 환경 변수값을 읽기
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, envMap.get(key));
}
// OS 환경 변수에 저장한다고 가정시
//DBURL = dev.db.com 개발서버
//DBURL = prod.db.com 운영서버
String dburl = System.getenv("DBURL");
}
}
- System.getenv() 를 사용하면 전체 OS 환경 변수를 Map 으로 조회할 수 있다.
- System.getenv(key) 를 사용하면 특정 OS 환경 변수의 값을 String 으로 조회할 수 있다.
실행결과

- OS 환경 변수를 설정하고, 필요한 곳에서 System.getenv() 를 사용하면 외부 설정을 사용할 수 있다.
- 이제 db 접근 url 과 같은 정보를 OS 환경변수에 설정해두고 읽어들이면 된다.
- 예를 들어서 개발 서버에서는 DBURL=dev.db.com 과 같이 설정하고, 운영 서버에서는 DBURL=prod.db.com 와 같이 설정하는 것이다.
- 이렇게 하면 System.getenv("DBURL") 를 조회할 때 각각 환경에 따라서 서로 다른 값을 읽게 된다.
하지만 OS 환경변수는 이 프로그램뿐 아니라 다른 프로그램도 사용할 수 있다. 즉 전역변수 같은 효과이다.
여러 프로그램에서 사용하는것이 맞을때도 있지만, 해당 애플리케이션을 사용하는 자바프로그램안에서만 사용하는 외부 설정값을
사용하고 싶을 때도 있다. 특정 자바 프로그램안에서 사용하는 외부 설정을 알아보자.
외부 설정 - 자바 시스템 속성 사용 예
자바 시스템 속성(Java System properties) 은 실행한 JVM 안에서 접근 가능한 외부 설정이다.
추가로 자바가 내부에서 미리 설정해두고 사용하는 속성들도 있다.
자바 시스템 속성은 다음과 같이 자바 프로그램을 실행할 때 사용한다.
- 예) java -Durl=dev -jar app.jar
- -D VM 옵션을 통해서 key=value 형식을 주면 된다. 이 예제는 url=dev 속성이 추가된다.
- 순서에 주의해야한다. -D 옵션이 -jar 보다 앞에 있다.
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)));
}
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);
}
}
- System.getProperties() 를 사용하면 Map 과 유사한(Map 의 자식타입) key=value 형식의 Properties 를 받을 수 있다.
- System.getProperty(key) 를 사용하면 속성값을 조회할 수 있다.
실행결과
22:19:03.735 [main] INFO hello.external.JavaSystemProperties - prop java.vm.specification.name=Java Virtual Machine Specification
22:19:03.736 [main] INFO hello.external.JavaSystemProperties - prop url=devdb
22:19:03.739 [main] INFO hello.external.JavaSystemProperties - prop sun.os.patch.level=
22:19:03.739 [main] INFO hello.external.JavaSystemProperties - prop native.encoding=MS949
22:19:03.739 [main] INFO hello.external.JavaSystemProperties - prop java.library.path=C:\Users\yhr05\.jdks\corretto-17.0.13\bin;C:\WINDOWS\Sun\Java\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\Users\yhr05\.jdks\temurin-21.0.5\bin;D:\01.tools\sts-4-4.19.0\contents\sts-4.19.0.RELEASE\plugins\org.apache.ant_1.10.12.v20211102-1452\bin;C:\Program Files\Common Files\Oracle\Java\javapath;C:\Program Files (x86)\Common Files\Oracle\Java\javapath;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;C:\Program Files\Git\cmd;C:\Program Files\Bandizip\;D:\dev\tools\apache-maven-3.8.5\bin;C:\Program Files\OpenSSL-Win64\bin;;C:\ProgramData\chocolatey\bin;C:\Program Files\Docker\Docker\resources\bin;C:\Users\yhr05\AppData\Local\Microsoft\WindowsApps;C:\Users\yhr05\AppData\Local\Programs\Fiddler;C:\Users\yhr05\AppData\Local\Programs\Microsoft VS Code\bin;C:\Users\yhr05\AppData\Local\GitHubDesktop\bin;.
22:19:03.739 [main] INFO hello.external.JavaSystemProperties - prop java.vm.info=mixed mode, sharing
22:19:03.739 [main] INFO hello.external.JavaSystemProperties - prop java.vendor=Amazon.com Inc.
22:19:03.739 [main] INFO hello.external.JavaSystemProperties - prop java.vm.version=17.0.13+11-LTS
22:19:03.739 [main] INFO hello.external.JavaSystemProperties - prop java.specification.maintenance.version=1
22:19:03.739 [main] INFO hello.external.JavaSystemProperties - prop sun.io.unicode.encoding=UnicodeLittle
22:19:03.740 [main] INFO hello.external.JavaSystemProperties - prop java.class.version=61.0
22:19:03.740 [main] INFO hello.external.JavaSystemProperties - prop username=dev_user
22:19:03.741 [main] INFO hello.external.JavaSystemProperties - url=devdb
22:19:03.741 [main] INFO hello.external.JavaSystemProperties - username=dev_user
22:19:03.741 [main] INFO hello.external.JavaSystemProperties - password=dev_pw
자바가 기본으로 제공하는 수많은 속성들이 추가되어 있는 것을 확인할 수 있다.
자바는 내부에서 필요할때 이런 속성들을 사용하는데, 예를 들어 file.encoding=UTF-8 를 통해서 기본적인 파일 인코딩 정보등으로 사용한다.
사용자가 직접 정의하는 자바 시스템 속성을 추가하였다.
url , username , password 를 조회하는 코드
실행할때 자바 시스템 속성을 추가해야한다.

1. Modify options를 선택한다.
2. Add VM options를 선택한다.
3. VM options에 다음을 추가한다.
- -Durl=devdb -Dusername=dev_user -Dpassword=dev_pw
jar 실행
jar 로 빌드되어 있다면 실행시 다음과 같이 자바 시스템 속성을 추가할 수 있다.
java -Durl=devdb -Dusername=dev_user -Dpassword=dev_pw -jar app.jar
test 해보기
빌드하기: boot-source-20230228\start\external> ./gradlew clean build
실행하기: boot-source-20230228\start\external\build\libs> java -Durl=devdb -Dusername=dev_user -Dpassword=dev_pw -jar external-0.0.1-SNAPSHOT.jar

자바 시스템 속성을 자바 코드로 설정하기
자바 시스템 속성은 앞서 본 것 처럼 `-D ` 옵션을 통해 실행 시점에 전달하는 것도 가능하고,
다음과 같이 자바 코드 내부에서 추가하는 것도 가능하다. 코드에 추가하면 이후에 조회시에 값을 조회할 수 있다.
- 설정: System.setProperty(propertyName, "propertyValue")
- 조회: System.getProperty(propertyName)
이 방식은 코드안에서 사용하는 것이기 때문에 외부로 설정을 분리하는 효과는 없다.
외부 설정 - 커맨드 라인 인수
커맨드 라인 인수(Command line arguments)는 애플리케이션 실행 시점에 외부 설정값을 main(args) 메서드의 args 파라미터로 전달하는 방법이다.
다음과 같이 사용한다.
예) java -jar app.jar dataA dataB
필요한 데이터를 마지막 위치에 스페이스로 구분해서 전달하면 된다. 이 경우 dataA dataB 2개의 문자가 args 로 전달된다.
package hello.external;
import lombok.extern.slf4j.Slf4j;
/**
* CommandLine 인수는 스페이스로 구분
* java -jar app.jar dataA dataB -> [dataA, dataB] 2
* java -jar app.jar url=devdb -> [url=devdb] 1개
* url=devdb 이라는 단어를 개발자가 직접 파싱해야 함
*/
@Slf4j
public class CommandLineV1 {
public static void main(String[] args) {
for (String arg : args) {
log.info("arg {}" , arg);
}
}
}
IDE에서 실행시 커맨드 라인 인수 추가

- 여기에 ` dataA dataB ` 를 입력하고 실행하자
- 커맨드 라인 인수는 공백(space)으로 구분한다.
실행결과

Jar 실행
jar로 빌드되어 있다면 실행시 다음과 같이 커맨드 라인 인수를 추가할 수 있다.
- java -jar project.jar dataA dataB
key=value 형식 입력
애플리케이션을 개발할 때는 보통 key=value 형식으로 데이터를 받는 것이 편리하다.
이번에는 커맨드라인 인수를 다음과 같이 입력하고 실행해보자
- ` url=devdb username=dev_user password=dev_pw `

실행결과 커맨드라인 인수는 key=value 형식이 아니다. 단순히 문자를 여러개 입력받는 형식이다.
그래서 3가지 문자가 입력되었다.
- url=devdb
- username=dev_user
- password=dev_pw
이것은 파싱되지 않은, 통문자이다.
이 경우 개발자는 = 을 기준으로 직접 데이터를 파싱해서 key=value 형식에 맞도록 분리해야한다.
그리고 형식이 배열이기 때문에 루프를 돌면서 원하는 데이터를 찾아야 하는 번거로움도 발생한다.
실제 애플리케이션 개발 시 주로 key=value 형식을 주로 사용하기 때문에 결국 파싱해서 Map 같은 형식으로 변환하도록 직접 개발해야하는 번거로움이 있다.
외부 설정 - 커맨드 라인 옵션 인수
일반적인 커맨드 라인 인수
커맨드 라인에 전달하는 값은 형식이 없고, 단순히 띄어쓰기로 구분한다.
- ` aaa bbb` ` [aaa, bbb]` 값 2개
- ` hello world` ` [hello, world]` 값 2개
- "hello world"` ` [hello world]` (공백을 연결하려면 ` "` 를 사용하면 된다.) 값 1개
- ` key=value` ` [key=value]` 값 1개
커맨드 라인 옵션 인수(command line option arguments)
커맨드 라인 인수를 key=value형식으로 구분하는 방법이 필요하다. 그래서 스프링에서는 커맨드 라인 인수를 key=value 형식으로 편리하게 사용할 수 있도록 스프링만의 표준방식을 정의했는데, 그것이 바로 커맨드 라인 옵션 인수이다.
스프링은 커맨드 라인에 -(dash) 2개(--) 를 연결해서 시작하면 key=value 형식으로 정하고 이것을 커맨드 라인 옵션 인수라 한다.
- `--key=value` 형식으로 사용한다.
- `--username=userA --username=userB` 하나의 키에 여러 값도 지정할 수 있다.
package hello.external;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class CommandLineV2 {
public static void main(String[] args) {
for (String arg : args) {
log.info("arg {}" , arg);
}
}
}


실행결과 자바에서 기본으로 제공하는 커맨드라인 인수로 통문자로 인식된다.
스프링에서 제공하는 key=value 를 파싱할 수 있도록하는 객체를 추가해보자,
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("NonOptionsArgs = {}" , appArgs.getNonOptionArgs());
log.info("OptionsName = {}" , 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 --url=devdb2 --username=dev_user --password=dev_pw mode=on
옵션 인수
-- 로 시작한다.
- --url=devdb
- --username=dev_user
- --password=dev_pw
옵션 인수가 아님
-- 로 시작하지 않는다.
- mode=on

실행결과
> Task :hello.external.CommandLineV2.main()
23:21:52.003 [main] INFO hello.external.CommandLineV2 - arg --url=devdb
23:21:52.012 [main] INFO hello.external.CommandLineV2 - arg --url=devdb2
23:21:52.012 [main] INFO hello.external.CommandLineV2 - arg --username=dev_user
23:21:52.012 [main] INFO hello.external.CommandLineV2 - arg --password=dev_pw
23:21:52.012 [main] INFO hello.external.CommandLineV2 - arg mode=on
23:21:52.032 [main] INFO hello.external.CommandLineV2 - SourceArgs = [--url=devdb, --url=devdb2, --username=dev_user, --password=dev_pw, mode=on]
23:21:52.032 [main] INFO hello.external.CommandLineV2 - NonOptionsArgs = [mode=on]
23:21:52.034 [main] INFO hello.external.CommandLineV2 - OptionsName = [password, url, username]
23:21:52.034 [main] INFO hello.external.CommandLineV2 - Option arg password=[dev_pw]
23:21:52.035 [main] INFO hello.external.CommandLineV2 - Option arg url=[devdb, devdb2]
23:21:52.035 [main] INFO hello.external.CommandLineV2 - Option arg username=[dev_user]
23:21:52.035 [main] INFO hello.external.CommandLineV2 - url=[devdb, devdb2]
23:21:52.035 [main] INFO hello.external.CommandLineV2 - username=[dev_user]
23:21:52.035 [main] INFO hello.external.CommandLineV2 - password=[dev_pw]
23:21:52.035 [main] INFO hello.external.CommandLineV2 - mode=null
- arg : 커맨드 라인의 입력 결과를 그대로 출력한다.
- SourceArgs : 커맨드 라인 인수 전부를 출력한다.
- NonOptionArgs = [mode=on] : 옵션 인수가 아니다.
- 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 를 스프링 빈으로 등록해둔다.
그리고 그 안에 입력한 커맨드 라인을 저장해둔다. 그래서 해당 빈을 주입 받으면 커맨드 라인으로 입력한 값을 어디서든 사용할 수 있다.
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;
// ApplicationArguments 구현체를 스프링부트가 뜰때 만들어줌
public CommandLineBean(ApplicationArguments applicationArguments) {
this.arguments = applicationArguments;
}
@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));
}
}
}
실행
커맨드라인 인수를 다음과 같이 입력하고 실행해보자.
옵션 수정 > 프로그램 인수
--url=devdb --username=dev_user --password=dev_pw mode=on

다음을 실행한다. ExternalApplication.main()
실행결과
2025-06-08T00:31:48.150+09:00 INFO 17880 --- [ main] hello.CommandLineBean : source [--url=devdb, --username=dev_user, --password=dev_pw, mode=on]
2025-06-08T00:31:48.151+09:00 INFO 17880 --- [ main] hello.CommandLineBean : optionNames [password, url, username]
2025-06-08T00:31:48.152+09:00 INFO 17880 --- [ main] hello.CommandLineBean : option args password=[dev_pw]
2025-06-08T00:31:48.152+09:00 INFO 17880 --- [ main] hello.CommandLineBean : option args url=[devdb]
2025-06-08T00:31:48.152+09:00 INFO 17880 --- [ main] hello.CommandLineBean : option args username=[dev_user]
입력한 커맨드 라인 인수, 커맨드 라인 옵션 인수 (key=value 형식)를 확인할 수 있다.
결론: 외부 설정으로 유연하고 안정적인 시스템을 만들자
환경에 따라 변하는 설정은 외부로 분리하고, 빌드 결과물은 고정시킨다.
이는 다음과 같은 장점이 있다.
- 빌드와 배포를 단순화
- 운영환경의 안전성과 테스트 신뢰도 향상
- 환경 추가 및 변경이 쉬움
유지 보수에 강한 시스템은 외부 설정을 적절히 분리한 시스템이다.
'Spring' 카테고리의 다른 글
| 로그인 처리1 - 쿠키, 세션 (0) | 2025.08.16 |
|---|---|
| Spring 외부 설정값 주입 방법 총정리 : Environment, @Value, @ConfigurationProperties 비교 (2) | 2025.06.11 |
| 모니터링 메트릭 활용 (1) | 2025.03.25 |
| Spring Boot 애플리케이션 실시간 모니터링: Actuator, Prometheus, Grafana 활용법 (0) | 2025.03.19 |
| Spring Boot + Redis 적용기 (0) | 2025.02.19 |