일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- 스프링 mvc2 - 타임리프
- 자바의 정석 기초편 ch11
- 자바의 정석 기초편 ch13
- 2024 정보처리기사 시나공 필기
- 스프링 mvc2 - 검증
- 자바의 정석 기초편 ch5
- 자바의 정석 기초편 ch8
- 게시글 목록 api
- @Aspect
- 스프링 db1 - 스프링과 문제 해결
- jpa 활용2 - api 개발 고급
- 2024 정보처리기사 수제비 실기
- 자바의 정석 기초편 ch7
- 자바의 정석 기초편 ch3
- 스프링 고급 - 스프링 aop
- 자바의 정석 기초편 ch6
- 자바의 정석 기초편 ch12
- 타임리프 - 기본기능
- 코드로 시작하는 자바 첫걸음
- 자바의 정석 기초편 ch4
- 자바의 정석 기초편 ch9
- 자바의 정석 기초편 ch14
- 스프링 db2 - 데이터 접근 기술
- 자바의 정석 기초편 ch2
- 자바의 정석 기초편 ch1
- 스프링 mvc2 - 로그인 처리
- 스프링 mvc1 - 서블릿
- 스프링 mvc1 - 스프링 mvc
- 스프링 입문(무료)
- jpa - 객체지향 쿼리 언어
- Today
- Total
나구리의 개발공부기록
외부설정과 프로필1, 프로젝트 설정 및 외부 설정이란?, 외부 설정(OS 환경 변수/자바 시스템 속성/커맨드 라인 인수/커맨드 라인 옵션 인수/커맨드 라인 옵션 인수와 스프링 부트/스프링 통합), 설정 데이터(외부 파일/내부 파일 분리/내부 파일 합체), 우선순위(설정 데이터/전체) 본문
외부설정과 프로필1, 프로젝트 설정 및 외부 설정이란?, 외부 설정(OS 환경 변수/자바 시스템 속성/커맨드 라인 인수/커맨드 라인 옵션 인수/커맨드 라인 옵션 인수와 스프링 부트/스프링 통합), 설정 데이터(외부 파일/내부 파일 분리/내부 파일 합체), 우선순위(설정 데이터/전체)
소소한나구리 2024. 12. 5. 14:35출처 : 인프런 - 스프링 부트 - 핵심 원리와 활용(유료) / 김영한님
유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용
1. 프로젝트 설정 및 외부 설정이란?
1) 프로젝트 설정
(1) 프로젝트 설정 - build.gradle
- 제공된 프로젝트를 사용
- Lombok과 테스트에서 롬복을 사용할 수 있는 설정을 추가
plugins {
id 'java'
id 'org.springframework.boot' version '3.0.2'
id 'io.spring.dependency-management' version '1.1.0'
}
group = 'hello'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
//test lombok 사용
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
}
tasks.named('test') {
useJUnitPlatform()
}
2) 외부 설정이란?
(1) 외부 설정
- 하나의 애플리케이션을 여러 다른 환경에서 사용해야 할 때가 있는데, 대표적으로 개발이 잘 진행되고 있는지 내부에서 확인하는 용도의 개발환경과 그리고 실제 고객에게 서비스하는 운영 환경이 있음
- 개발 환경: 개발 서버, 개발 DB 사용
- 운영 환경: 운영 서버, 운영 DB 사용
- 문제는 각각의 환경에 따라서 서로 다른 설정값이 존재한다는 점임
- 예를 들어 애플리케이션이 개발 DB에 접근하려면 dev.db.com이라는 URL이라는 정보가 필요하다고 할 때, 운영 DB에 접근하려면 전혀 다른 URL인 prod.db.com이라는 정보가 필요할 수 있는 것임
(2-1) 가장 단순한 해결방법 - 과거의 해결방법으로 권장되지 않음
- 이미지와 같이 각각의 환경에 맞게 애플리케이션을 여러번 빌드 하여 해결
- 개발 환경에는 dev.db.com이 필요하므로 이 값을 애플리케이션 코드에 넣은 다음 빌드해서 개발app.jar를 만들고, 운영 환경에는 prod.db.com이 필요하므로 해당 값을 애플리케이션 코드에 넣은 다음에 빌드해서 운영 app.jar를 만들면 됨
- 이렇게 각각의 환경에 맞는 배포파일을 만들어서 배포하면 되는데 좋은 방법이 아님
(2-2) 환경따라 변하는 설정값을 내부에 포함했을 때 안좋은 점
- 1. 일단 환경에 따라서 빌드를 여러번 해야 한다는 것 자체가 번거로움
- 2. 개발 버전과 운영 버전의 빌드 결과물이 다르기 때문에 개발 환경에서 검증이 되었더라도 운영 환경에서 다른 빌드 결과를 사용하기 때문에 예상치 못한 문제가 발생할 수 있음
- 즉, 개발용 빌드가 끝나고 검증한 다음에 운영용 빌드를 해야하는데 그 사이에 github등의 수정으로 누군가가 코드를 변경할 수 있는 위험이 존재하기에 정말로 같은 소스코드에서 나온 결과물인지 검증하기가 어려움
- 물론 정말 잘 관리해서 이런 문제가 없게끔 할 수도 있겠지만 한번 문제가 발생했을 때 문제를 찾기가 어려울 수 있음
- 3. 각 환경에 맞추어 최종 빌드가 된 결과물은 다른 환경에서 사용할 수 없어서 유연성이 떨어지게되며 향후 다른 환경이 필요하면 그곳에 맞도록 또 소스코드를 수정하고 빌드를 해야함
(3) 환경에 따라 변하는 설정값을 실행 시점에 주입 - 권장되는 방식, 대부분 실무에서 이렇게 적용함
- 배포 환경과 무관하게 하나의 빌드 결과물을 만들며(app.jar) 이 안에는 설정값이 없음
- 설정값은 실행 시점에 각 환경에 따라 외부에서 주입하며 개발 서버는 dev.db.com값을, 운영 서버는 prod.db.com값을 외부 설정으로 주입
- 이렇게 하면 빌드도 한번만 하면 되고 개발 버전과 운영 버전의 빌드 결과물이 같기 때문에 개발 환경에서 검증되면 운영 환경에서도 믿고 사용할 수 있게됨
- 그리고 이후에 새로운 환경이 추가되어도 별도의 빌드 과정이 없이 기존 app.jar를 사용하여 손쉽게 새로운 환경을 추가할 수 있음
(4) 유지보수하기 좋은 애플리케이션 개발의 기본 원칙 - ** 중요!
- 유지보수하기 좋은 애플리케이션을 개발하는 단순하면서도 중요한 원칙은 변하는 것과 변하지 않는 것을 분리하는 것
- 각 환경에 따라 변하는 외부 설정값은 분리하고, 변하지 않는 코드와 빌드 결과물을 유지함으로써 덕분에 빌드 과정을 줄이고 환경에 따른 유연성을 확보하게 되었음
- 클래스든 컴포넌트든 설정 값이든 어떤 것이든 개발할 때에는 변하는 것과 변하지 않는 부분을 딱 제대로 잘 분리해내는 것이 유지보수하기 좋은 애플리케이션을 개발하는 것임
(5) 외부 설정 주입 방법 - 크게 4가지
- OS 환경 변수: OS에서 지원하는 외부 설정, 해당 OS를 사용하는 모든 프로세스에서 사용
- 자바 시스템 속성: 자바에서 지원하는 외부 설정, 해당 JVM안에서 사용
- 자바 커맨드 라인 인수: 커맨드 라인에서 전달하는 외부 설정, 실행시 main(args)메서드에서 사용
- 외부 파일(설정 데이터): 프로그램에서 외부 파일을 직접 읽어서 사용
- 애플리케이션에서 특정 위치의 파일을 읽도록 해둔 후 각 서버마다 해당 파일안에 다른 설정 정보를 남겨두어 애플리케이션이 각 환경에서 파일의 값을 읽어 동작하도록하는 방식
- 개발 서버 예시 hello.txt - url:dev.db.com
- 운영 서버 예시 hello.txt - url:prod.db.com
2. 외부 설정 - OS 환경 변수
1) OS 환경 변수
(1) OS 환경 변수란?
- OS 환경 변수(OS environment variables)는 해당 OS를 사용하는 모든 프로그램에서 읽을 수 있는 설정값임
- 다른 외부 설정과 비교해서 사용 범위가 가장 넓음
(2) 조회 방법
- 윈도우 OS: set
- MAC, 리눅스 OS: printenv
(3) 설정 방법
- 윈도우 환경 변수, mac 환경 변수 등으로 검색해보면 수 많은 예시를 확인할 수 있으로 필요할 때 검색해서 적용
(4) OsEnv - OS 환경 변수의 값 읽어보기
- test.java.hello하위에 external 패키지를 생성 후 작성
- System.getenv()를 사용하면 전체 OS 환경 변수를 Map으로 조회할 수 있음
- System.getenv(key)를 사용하면 특정 OS 환경 변수의 값을 String으로 조회할 수 있음
package hello.external;
@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));
}
}
}
(5) 사용 예시
- OS 환경 변수를 설정하고 필요한 곳에서 System.getenv()를 사용하여 외부 설정을 사용할 수 있으므로 데이터베이스 접근 URL과 같은 정보를 OS 환경 변수에 설정해두고 읽어들이면 됨
- 예를 들어 개발 서버에는 DBURL=dev.db.com과 같이 설정하고 운영 서버에는 DBURL=prod.db.com와 같이 설정해두면System.getenv("DBURL")을 조회할 때 각각 환경에 따라 서로 다른 값을 읽게 됨
- 하지만 OS 환경 변수는 해당 프로그램 뿐만 아니라 다른 프로그램에서 사용할 수 있어 전역 변수 같은 효과가 있음
- 여러 프로그램에서 사용하는 것이 맞을 때도 있지만 해당 애플리케이션을 사용하는 자바 프로그램 안에서만 사용되는 외부 설정값을 사용하고 싶을 때에는 자바 시스템 속성을 사용하면 됨
// DBURL = dev.db.com(개발서버)
// DBURL = prod.db.com(운영서버)
System.getenv("DBURL");
3. 외부 설정 - 자바 시스템 속성
1) 자바 시스템 속성
(1) 자바 시스템 속성이란?
- 자바 시스템 속성(Java System properties)은 실행한 JVM 안에서 접근 가능한 외부 설정임
- 추가로 자바가 내부에서 미리 설정해두고 사용하는 속성들도 있음
- 자바 시스템 속성은 아래와 같이 자바 프로그램을 실행할 때 사용함
- 예) java -Durl=dev -jar app.jar, -D VM옵션을 통해서 key=value 형식을 주면되며 해당 예제는 url=dev속성이 추가됨
- 순서에 주의해야하며 -D옵션이 -jar보다 앞에 있어야 함
(2) JavaSystemProperties - 자바 시스템 속성 값 읽어보기
- System.getProperties()를 사용하면 Map과 유사한(Map의 자식 타입) key=value형식의 Properties를 받을 수 있으며 이것을 통해서 모든 자바 시스템 속성을 조회할 수 있음
- System.getProperty(key)를 사용하여 속성값을 조회할 수 있음
- 실행해보면 자바가 기본으로 제공하는 수 많은 속성들이 추가되어 있는 것을 확인할 수 있으며 자바는 내부에서 필요할 때 이런 속성들을 사용함
package hello.external;
@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)));
}
}
}
(3) JavaSystemProperties - 사용자가 직접 정의하는 자바 시스템 속성을 추가
- url, username, password를 조회하는 코드를 추가
- VM options에 -Durl=devdb -Dusername=dev_user -Dpassword=dev_pw 추가
package hello.external;
@Slf4j
public class JavaSystemProperties {
public static void main(String[] args) {
// ... 기존 코드 생략
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);
}
}
(4) 실행 결과
- 실행해보면 VM options로 설정한 값이 자바 시스템 속성에 입력되어 로그로 출력되는 것을 확인할 수 있음
(5) Jar 실행
- Jar로 프로젝트가 빌드 되어있다면 빌드된 파일을 실행 할 때 아래처럼 자바 시스템 속성을 추가할 수 있음
- java -Durl=devdb -Dusername=dev_user -Dpassword=dev_pw -jar external-0.0.1-SNAPSHOT.jar
- 지금 예제에서는 test하위의 프로젝트에 main메서드를 입력하였기 때문에 실행하여도 출력되지 않지만 main하위에 별도의 클래스를 생성하여 지금 작성한 코드를 메서드로 만들어서 main메서드에 호출하면 아래의 출력결과처럼 추가된 자바 시스템 속성값을 확인할 수 있음
(6) JavaSystemProperties - 자바 시스템 속성을 자바 코드로 설정하기
- 자바 시스템 속성은 -D 옵션을 통해 실행 시점에 전달하는 것도 가능하지만 자바 코드 내부에서 추가하는 것도 가능함
- 코드에서 추가하면 이후에 조회시에 값을 조회할 수 있음
- 그러나 이 방식은 코드 안에서 사용하는 것이기 때문에 외부로 설정을 분리하는 효과는 없음
package hello.external;
@Slf4j
public class JavaSystemProperties {
public static void main(String[] args) {
// ... 기존 코드 생략
String hello = System.setProperty("hello_key", "hello_value");
String helloKey = System.getProperty("hello_key");
log.info("helloKey={}", helloKey);
}
}
4. 외부 설정 - 커맨드 라인 인수
1) 커맨드 라인 인수
(1) 커맨드 라인 인수란?
- 커맨드 라인 인수(command line arguments)는 애플리케이션 실행 시점에 외부 설정값을 main(args) 메서드의 args 파라미터로 전달하는 방법임
- 'java -jar app.jar dataA dataB' 처럼 사용하며 필요한 데이터를 마지막 위치에 스페이스로 구분해서 전달하면 됨
(2) CommandLineV1
- 커맨드 라인 인수를 출력
- IDE에서 실행 시 아래의 이미지 처럼 커맨드 라인 인수를 입력하는 곳에 값을 입력
- 커맨드 라인 인수는 공백으로 구분함, dataA dataB
- 실행해보면 로그로 정상적으로 입력한 인수들이 출력되는 것을 확인할 수 있음
package hello.external;
@Slf4j
public class CommandLineV1 {
public static void main(String[] args) {
for (String arg : args) {
log.info("args {}", arg);
}
}
}
** 참고
- 만약 실행 설정인 Edit Configurations...에 입력창이 안보이면 Modify options 버튼을 눌러서 찾아보면 입력창을 추가할 수 있음
(3) Jar 실행
- Jar로 빌드되어 있다면 아래처럼 커맨드 라인 인수를 추가할 수 있음
- java -jar 빌드파일.jar 입력데이터1 입력데이터2 ... (공백으로 구분)
(4) 문제점, key=value 파싱이 안됨
- 애플리케이션을 개발할 때는 보통 key-value 형식으로 데이터를 받는 것이 편리한데, 커맨드 라인 인수는 파싱되지 않은 통 문자임
- 즉, key=value형식으로 커맨드 라인 인수를 입력하면 생긴것만 key=value 형태이지 그냥 문자라는 뜻이므로 개발자가 직접 =을 기준으로 데이터를 파싱해서 key=value 형식에 맞도록 분리해야함
- 그리고 형식이 배열이기 때문에 루프를 돌면서 원하는 데이터를 찾아야하는 번거로움도 발생함
- 실제 애플리케이션을 개발할 때는 주로 key=value 형식을 자주 사용하기 때문에 결국 파싱해서 Map과 같은 형식으로 변환해야하는 번거로움이 있음
5. 외부 설정 - 커맨드 라인 옵션 인수
1) 커맨드 라인 옵션 인수
(1) 커맨드 라인 옵션 인수(command line option arguments)
- 일반적인 커맨드 라인에 전달하는 값은 형식이 없고 단순히 띄어쓰기로 구분하며 key=value로 입력하더라도 통으로 문자로 인식됨
- 대부분의 개발자는 key=value의 데이터 형식을 선호하기 때문에 스프링에서는 커맨드 라인 인수를 key=value 형식으로 편리하게 사용할 수 있도록 스프링 만의 표준 방식을 정의했는데 그것이 바로 커맨드 라인 옵션 인수임
- 스프링은 커맨드 라인데 -(dash)를 2개 연결해서 시작하면 key=value 형식으로 꺼낼 수 있음
- --key=value 형식으로 사용하며 --key1=value1 --key1=value2 처럼 하나의 키에 여러 값도 지정할 수 있음
(2-1) CommandLineV2
- 스프링이 제공하는 ApplicationArguments 인터페이스와 DefaultApplicationArguments 구현체를 사용하면 커맨드 라인 옵션 인수를 규격대로 파싱해서 편리하게 사용할 수 있음
- 이해를 돕기 위해 --(dash)가 없는 값도 추가하여여 아래처럼 커맨드 라인 인수를 입력하고 실행하면 옵션 인수와 옵션 인수가 아닌 것이 구분되어 출력됨
- --url=devdb --username=dev_user --password=dev_pw mode=on
package hello.external;
@Slf4j
public class CommandLineV2 {
public static void main(String[] args) {
for (String arg : args) {
log.info("args {}", arg);
}
ApplicationArguments appArgs = new DefaultApplicationArguments(args);
log.info("sourceArgs = {}", List.of(appArgs.getSourceArgs())); // 값 출력, 참조값이므로 List으로 출력
log.info("NonOptionArg = {}", appArgs.getNonOptionArgs()); // dash 없는 값만 출력
log.info("OptionsNames = {}", appArgs.getOptionNames()); // dash 붙은 값의 모든 키값을 출력
Set<String> optionNames = appArgs.getOptionNames();
for (String optionName : optionNames) {
log.info("option arg {}={}", optionName, appArgs.getOptionValues(optionName)); // dash 붙은 값의 key와 대응되는 value 출력
}
List<String> url = appArgs.getOptionValues("url");
List<String> username = appArgs.getOptionValues("username");
List<String> password = appArgs.getOptionValues("password");
List<String> mode = appArgs.getOptionValues("mode"); // 옵션 인수가 아닌 것을 꺼내면 null 반환
log.info("url={}", url);
log.info("username={}", username);
log.info("password={}", password);
log.info("mode={}", mode);
}
}
(2-2) 옵션 인수, --로 시작
- --url=devdb
- --username=dev_user
- --password=dev_pw
(2-3) 옵션 인수가 아님, --로 시작하지 않음
- mode=on
(2-4) 메서드 분석
- getSourceArgs(): 커맨드 라인 인수 전부를 출력, 참조값으로 반환되므로 List로 변환해서 출력해야 함
- getNonOptionsArgs(): 옵션 인수가 아닌 값을 모두 출력(--를 사용하지 않음)
- getOptionNames(): 옵션 인수의 key 값을 모두 출력(--를 사용)
- getOptionValues(key): 옵션 인수의 key값을 입력하면 대응되는 value를 모두 출력, 옵션 인수의 값이 아닌 것은 조회할 수 없어서 null이 반환됨
** 참고
- 옵션 인수는 하나의 키에 여러 값을 포함할 수 있기 때문에 getOptionValues(key)의 결과는 리스트를 반환함
- 커맨드 라인 옵션 인수는 자바 언어의 표준 기능이 아니며 스프링이 편리함을 위해 제공하는 기능임
6. 외부 설정 - 커맨드 라인 옵션 인수와 스프링 부트
1) 커맨드 라인 옵션 인수와 스프링 부트
(1) 커맨드 라인 옵션 인수와 스프링 부트
- 스프링 부트는 커맨드 라인을 포함하여 커맨드 라인 옵션 인수를 활용할 수 있는 ApplicationArguments를 스프링 빈으로 등록해두고 그 안에 입력한 커맨드 라인을 저장해둠
- 그래서 해당 빈을 주입 받으면 커맨드 라인으로 입력한 값을 어디서든 사용할 수 있음
(2) CommandLineBean
- src/main 하위에 작성
- 코드 작성 후 '--url=devdb --username=dev_user --password=dev_pw mode=on' 처럼 인수를 입력한 후 애플리케이션을 실행해보면 각 메서드의 결과에 따라 커맨드 라인 인수와 커맨드 라인 옵션 인수가 출력되는 것을 확인할 수 있음
package hello;
@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));
}
}
}
7. 외부 설정 - 스프링 통합
1) 스프링 통합
(1) 추상화
- 지금까지 살펴본 커맨드 라인 옵션 인수, 자바 시스템 속성, OS 환경변수는 모두 외부 설정을 key=value 형식으로 사용할 수 있는 방법임
- 그러나 이 외부 설정값을 읽어서 사용해야하는 개발자 입장에서는 모두 key=value형식이고 설정값을 외부로 뽑아 둔 것인데 어디에 있는 외부 설정값을 읽어야 하는지에 따라서 읽는 방법이 전부 다름
- 만약 OS에 환경 변수를 두었는데 이후에 정책이 변경되어 자바 시스템 속성에 환경 변수를 두기로 했다고하면 해당 코드를 모두 변경해야함
- 외부 설정값이 어디에 위치하든 상관없이 일관성있고 편리하게 key=value형식의 외부 설정값을 읽을 수 있으면 사용하는 개발자 입장에서 더 편리하게 되고 외부 설정값을 설정하는 방법도 더 유연해 질 수 있는데, 스프링은 이 문제를 Environment와 PropertySource라는 추상화를 통해서 해결했음
(2) 스프링의 외부 설정 통합
(3) PropertySource - 추상 클래스
- org.springframework.core.env;
- 스프링은 PropertySource라는 추상 클래스를 제공하고 각각의 외부 설정을 조회하는 XxxPropertySource 구현체들을 만들어 두었음
- 스프링은 로딩 시점에 필요한 PropertySource들을 생성하고 Environment에서 사용할 수 있게 연결해둠
(4) Environment - 인터페이스
- package org.springframework.core.env;
- PropertyResolver를 상속하였음
- Environment를 통해서 특정 외부 설정에 종속되지 않고 일관성 있게 key=value 형식의 외부 설정에 접근할 수 있음
- environment.getProperty(key)를 통해서 값을 조회할 수 있으며 Environment는 내부에서 여러 과정을 거쳐서 PropertySource들에 접근함
- 같은 값이 있을 경우를 대비해서 스프링이 미리 우선순위를 정해두었음
- 스프링을 사용하면 모든 외부 설정은 Environment를 통해서 조회하면 됨
(5) 설정 데이터(파일)
- 우리가 잘 아는 application.properties, application.yml도 PropertySource에 추가되며 Environment를 통해서 접근할 수 있음
(6) EnvironmentCheck
- 커맨드 라인 옵션 인수 실행: --url=devdb --username=dev_user --password=dev_pw
- 자바 시스템 속성 실행: -Durl=devdb -Dusername=dev_user -Dpassword=dev_pw
- 인수들의 입력 위치는 다르지만 모두 동일한 코드로 조회되는 것을 확인할 수 있음
package hello;
@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);
}
}
(7) 정리
- 커맨드 라인 옵션 인수, 자바 시스템 속성 모두 Environment를 통해서 동일한 방법으로 읽을 수 있는 것을 확인할 수 있음
- 스프링은 Environment를 통해서 외부 설정을 읽는 방법을 추상화한 덕분에 자바 시스템 속성을 사용하다가 만약 커맨드 라인 옵션 인수를 사용하도록 읽는 방법이 변경되어도 개발 소스 코드는 전혀 변경하지 않아도됨
2) 우선순위
(1) 우선순위 간략 설명
- 커맨드 라인 옵션 인수와 자바 시스템 속성을 중복해서 설정하면 커맨드 라인 옵션 인수로 입력한 값이 출력되며 각 인수 입력 방법마다 우선순위가 적용되는 것을 확인할 수 있음
- 우선 순위는 상식 선에서 딱 2가지만 기억하면 됨
- 1. 더 유연한 것이 우선권을 가짐, (변경하기 어려운 파일 보다 실행시 원하는 값을 줄 수 있는 자바 시스템 속성이 더 우선권을 가짐)
- 2. 범위가 더 넓은 것보다 좁은 것이 우선권을 가짐 (자바 시스템 속성은 해당 JVM안에서 모두 접근할 수 있으나 커맨드 라인 옵션 인수는 main의 args를 통해서 들어오기 때문에 접근 범위가 더 좁음)
- 즉, 자바 시스템 속성과 커맨드 라인 옵션 인수의 경우 커맨드 라인 옵션 인수의 범위가 더 좁기 때문에 커맨드 라인 옵션 인수가 우선권을 가짐
- 우선순위에 대해서는 마지막에 더 자세히 다룸
8. 설정 데이터 - 외부 파일
1) 외부 파일
(1) 등장 이유
- 지금까지 학습한 OS 환경 변수, 자바 시스템 속성, 커맨드 라인 옵션 인수는 사용해야 하는 값이 늘어날 수록 사용하기가 불편해짐
- 실무에서는 수십개의 설정값을 사용하기도 하므로 이런 값들을 프로그램을 실행할 때 마다 입력하게 되면 번거롭고 관리도 어려움
- 그래서 등장하는 대안으로 설정값을 파일에 넣어서 관리하는 방법임
- 애플리케이션 로딩 시점에 해당 파일을 읽어들이면 되며 그 중에서도 .properties라는 파일은 key=value 형식을 사용해서 설정값을 관리하기에 아주 적합함
(2) 실행 시점에 외부 설정 파일 조회
- 이미지 처럼 개발 서버와 운영 서버 각각에 application.properties라는 같은 이름의 파일을 준비해 두면 애플리케이션 로딩 시점에 해당 파일을 읽어서 그 속에 있는 값들을 외부 설정값으로 사용하면 됨
- 물론 각 서버에 위치한 application.properties의 파일 내부 값은 각 서버에서 사용할 값으로 입력해야함
- 파일 이름이 같기 때문에 애플리케이션 코드는 그대로 유지할 수 있음
** 참고
- 자바 코드 내부에 있는 application.properties를 말하는 것이 아니라 자바 코드 외에 별도의 파일로 application.properties라는 별도의 파일을 생성한 것임
- 지금 부터 설명하는 내용은 application.properties 대신에 yml형식의 application.yml에도 동일하게 적용됨(yml은 뒤에서 자세히 설명함)
(3) 스프링과 설정 데이터
- 원래는 개발자가 파일을 읽어서 설정값으로 사용할 수 있도록 개발을 해야하지만 스프링 부트가 이미 이런 부분을 다 구현해 두었음
- 개발자는 application.properties라는 이름의 파일을 자바를 실행하는 위치에 만들어 두기만하면 스프링이 해당 파일을 읽어서 사용할 수 있는 PropertySource의 구현체를 제공함
- 스프링에서는 이러한 application.properties 파일을 설정 데이터(Config data)라고 하며 설정 데이터도 Environment를 통해서 조회할 수 있음
(4) 동작 확인
- ./gradlew clean build로 프로젝트를 빌드
- build/libs로 이동
- 해당 위치에 application.properties 파일 생성 후 아래의 데이터를 입력
- java -jar external-0.0.1-SNAPSHOT.jar 실행
- 실행 결과를 보면 파일에 입력한값을 애플리케이션이 동작하여 조회한 결과를 정상적으로 출력하는 것을 확인할 수 있음
- 이렇게 각각의 환경에 따라 설정 파일의 내용을 다르게 준비하면 되며 이 덕분에 설정값의 내용이 많고 복잡해도 파일로 편리하게 관리할 수 있음
url=dev.db.com
username=dev_user
password=dev_pw
(5) 남은 문제
- 외부 설정을 별도의 파일로 관리하게 되면 설정 파일 자체를 관리하기 번거로운 문제가 발생함
- 서버가 10대면 변경사항이 있을 때 10대 서버의 설정 파일을 모두 각각 변경해야 하는 불편함이 있음
- 설정 파일이 별도로 관리되기 때문에 설정값의 변경 이력을 확인하기 어려우며 특히 설정값의 변경 이력이 프로젝트 코드들과 어떻게 영향을 주고 받는지 그 이력을 같이 확인하기 어려움
- 그러나 이렇게 설정파일을 외부 파일로 두어야만 하는 경우가 있기 때문에 이 방법도 알아두어야 함
9. 설정 데이터 - 내부 파일 분리
1) 내부 파일 분리
(1) 외부 파일 설정 단점의 문제 해결
- 설정 파일을 외부에 관리하는 것은 설정을 변경할 때 마다 서버에 들어가서 각각의 변경 사항을 수정해두어야 하기에 상당히 번거로움
- 물론 이것을 자동화하기 노력할 순 있으나 그것 조차 개발자의 리소스가 들어가는 일임
- 이 문제는 설정 파일을 프로젝트 내부에 포함해서 관리하고 빌드 시점에 함께 빌드되게 하면 간단히 해결할 수 있음
- 이렇게 하면 애플리케이션을 배포할 때 설정 파일의 변경 사항도 함께 배포할 수 있으며 쉽게 이야기하면 jar 하나로 설정 데이터까지 포함해서 관리하는 것임
(2) 실행 시점에 내부 설정 파일 조회
- 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를 읽어서 사용 - 스프링은 이미 설정 데이터를 내부에 파일로 분리해두고 외부 설정값(프로필)에 따라 각각 다른 파일을 읽는 방법을 다 구현해 두었음
(3) 스프링과 내부 설정 파일 읽기
- main/resources에 다음 파일들을 추가
application-dev.properties 생성: 개발 프로필에서 사용
url=dev.db.com
username=dev_user
password=dev_pw
application-prod.properties 생성: 운영 프로필에서 사용
url=prod.db.com
username=prod_user
password=prod_pw
(4) 프로필
- 스프링은 이럴 때 사용하기 위해 프로필이라는 개념을 지원하는데, spring.profiles.active 외부 설정에 값을 넣으면 해당 프로필을 사용한다고 판단함
- 프로필에 따라서 아래와 같은 규칙으로 해당 프로필에 맞는 내부 설정(설정 데이터)을 조회함
- application-{profile}.properties
(5) 실행
- IDE에서 커맨드 라인 옵션 인수로 실행
- --spring.profiles.active=dev : 개발 프로필이 동작하여 application-dev.properties파일의 값이 출력됨
- --spring.profiles.active=prod : 운영 프로필이 동작하여 application-prod.properties파일의 값이 출력됨
- -D 옵션으로 자바 시스템 속성으로 실행 해도 동일한 결과가 출력됨
- -Dspring.profiles.active=dev
- -Dspring.profiles.active=prod
- 빌드된 Jar 파일로 실행해도 당연히 동일한 결과가 출력됨
- java -Dspring.profiles.active=dev -jar external-0.0.1-SNAPSHOT.jar
- java -jar external-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
- java -Dspring.profiles.active=prod -jar external-0.0.1-SNAPSHOT.jar
- java -jar external-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod
- 출력 로그에서 어떤 프로필이 활성화 되었는지 로그로 함께 확인할 수 있음
(6) 남은 문제
- 설정 파일을 각각 분리해서 관리하면 한눈에 전체가 들어오지 않는 단점이 있음
10. 설정 데이터 - 내부 파일 합체
1) 내부 파일 합체
(1) 내부 파일 분리 단점 해결
- 설정 파일을 각각 분리해서 관리하면 한눈에 전체가 들어오지 않는 단점이 존재함
- 스프링은 이런 단점을 보완하기 위해서 물리적인 하나의 파일 안에서 논리적으로 영역을 구분하는 방법을 제공함
- 가장 많이 사용하는 방법임
(2) 하나의 파일로 통합
- 기존에는 각 환경마다 설정파일을 분리해서 관리했으나 스프링은 하나의 application.properties 파일 안에서 논리적으로 영역을 구분하는 방법을 제공함
- application.properties 구분방법: #--- 또는 !--- (#이나 ! + dash 3개)
- application.yml 구분 방법: --- (dash만 3개) - 프로필에 따라 논리적으로 구분된 설정 데이터를 활성화 할 수 있음
- spring.config.activate.on-profile=프로필 값 - 이미지의 application.properties는 하나의 파일이지만 내부에 2개의 논리 문서로 구분되어있으며 각 프로필이 활성화 되면 해당되는 설정 데이터가 사용됨
(3) 설정 데이터를 하나의 파일로 통합
- 기존에 작성하였던 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개의 하이픈 문자가 있어야함
- 파일을 분할하는 주석 위아래에는 주석을 적으면 안됨
(4) 실행
- 내부 파일 분리에서 실행했던 것처럼 커맨드 라인 옵션 인수 실행, 자바 시스템 속성 실행, Jar 실행 모두 정상적으로 프로필이 적용되어 해당 파일 값이 조회되는 것을 확인할 수 있음
- 스프링 부트로 프로젝트를 설정하면 애초에 해당파일이 생성되며 실무에서는 보통 하나의 설정파일로 많이 사용하고 만약 이런 설정을 잘 다루지 못하면 하나의 설정파일로 사용하는 것이 가장 편리하기에 해당 방법을 권장함
- 그러나 개발 상황이나 취향에 따라 외부 설정파일을 사용해야만 할 때가 있고 내부 설정 파일을 분리해서 사용하는 경우도 있음
11. 우선 순위 - 설정 데이터
1) 설정 데이터
(1) 기본값 - application.properties 수정
- 위에서 설정한 application.properties의 설정값이 입력된 상태에서 애플리케이션을 실행할 때 인수로 프로필을 적용하지 않는다면 해당되는 프로필이 없으므로 키를 각각 조회된 값이 null로 조회됨
- 실행 결과의 첫줄을 보면 활성 프로필이 없어서 default라는 이름의 프로필이 활성화 되는 것을 확인할 수 있는데 즉, 프로필을 지정하지 않고 실행하면 스프링은 기본으로 default라는 이름을 프로필을 사용함
- 내 PC에서 개발하는 것을 보통 로컬(local) 개발 환경이라고 하는데 이때도 항상 프로필을 지정하면서 실행하는 것은 상당히 번거로운 일이므로 설정 데이터에는 기본값을 지정할 수 있으며 이 값은 프로필 지정과 무관하게 항상 사용됨
- 스프링은 문서를 위에서 아래로 순서대로 읽으면서 설정하며 처음에 입력된 논리 문서는 프로필 설정을 해주지 않았으므로 프로필과 무관하게 설정 데이터를 읽어서 사용함
- 이렇게 프로필 지정과 무관하게 사용되는 것을 기본값이라고 함
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
(2) 실행
- 프로필을 지정하지않고 실행해보면 기본값 프로필이 적용되어 local_xxx의 값들이 출력되는 것을 확인할 수 있음
- 프로필을 지정하고 실행하면 당연히 프로필을 준 부분이 우선권을 가져 해당 프로필이 실행되고 해당 프로필이 적용된 논리 문서의 값들이 조회되는 것을 확인할 수 있음
2) 설정 데이터 적용 순서
(1) 적용 순서 설명(위의 예제를 예시로 설명)
- 스프링은 단순하게 문서를 위에서 아래로 순서대로 읽으면서 사용할 값을 설정함
- 1. 스프링은 순서상 위에 있는 local 관련 논리 문서의 데이터들을 읽어서 설정
- 여기에는 별도의 프로필을 지정하지 않았기 때문에 프로필과 무관하게 항상 값을 사용하도록 설정함 - 2. 그 다음 순서로 dev 관련 논리 문서를 읽음
- 만약 dev 프로필이 설정되어있다면 기존 데이터를 dev관련 논리 문서의 값으로 대체함
- dev 프로필을 사용하지 않는다면 dev관련 논리 문서는 무시되고 그 값도 사용하지 않음 - 3. 다음 순서로 prod 관련 논리 문서를 읽음
- 2번과 동일하게 동작함
** 참고
- 프로필을 한번에 둘 이상 설정하는 것도 가능함 ','로 구분
- ex) --spring.profiles.active=dev,prod
(2-1) 극단적인 예시를 통해 이해하기
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
- 위의 설정처럼 맨 마지막에 논리 문서를 추가하여 프로필을 설정하지 않고 url=hello.db.com을 입력하였을 때 순서를 설명
- 물론 이렇게 사용하는 것은 의미가 없으며 이해를 돕기 위해서 극단적인 예제를 설정한 것이며 보통은 기본값을 처음에 두고 그 다음에 프로필이 필요한 논리 문서들을 둠
- 1. 스프링은 처음에 local 관련 논리 문서의 데이터들을 읽어서 설정하며 별도의 프로필을 지정하지 않았기 때문에 프로필과 무관하게 항상 값이 설정됨
- 2. 다음 순서로 dev 관련 논리 문서를 읽는데 dev관련 프로필이 설정되었으면 dev 관련 논리 문서의 값으로 대체
- 3. 다음 순서로 prod 관련 논리 문서를 읽고 2번과 동일한 방법으로 동작함
- 4. 마지막으로 hello 관련 논리 문서의 데이터를 읽어서 설정하는데 여기에는 별도의 프로필을 설정하지 않았기 때문에 프로필과 무관하게 항상 값이 설정됨
(2-2) 극단적인 예시 출력결과
- 프로필을 설정하지 않고 출력했을 때: 기본값이 적용되어 local_xxx의 값이 출력되었지만 마지막에 url=hello.db.com 설정이 있으므로 url의 정보의 값만 해당 값으로 덮어쓰여 아래와 같이 출력됨
- 프로필을 prod로 설정하고 출력했을 때: 위와 마찬가지로 prod 프로필이 적용되어 prod_xxx의 값이 출력되었지만 url 정보만 hello.db.com이 출력된 것이 확인됨
// 프로필을 설정하지 않고 출력했을 때
env url=hello.db.com
env username=local_user
env password=local_pw
// 프로필을 prod로 설정하고 출력했을 때
env url=hello.db.com
env username=prod_user
env password=prod_pw
(2-3) 정리
- 단순하게 문서를 위에서 아래로 순서대로 읽으면서 값을 설정하고 이때 기존 데이터가 있으면 덮어씀
- 논리 문서에 프로필을 설정하는 옵션이 있으면 해당 프로필을 사용할 때만 논리 문서를 적용함
- 프로필이 적용되는 논리 문서에서 일부의 설정만 적용하고 해당 프로필로 애플리케이션을 실행하면 해당 프로필로 적용했던 설정만 논리 문서의 값으로 대체되고 나머지의 값을 기본값이 유지됨
- 즉, 스프링의 우선순위에 따른 설정값은 대부분의 지금과 같이 기존 데이터를 변경하는 방식으로 적용됨
12. 우선 순위 - 전체
1) 외부 설정 우선 순위
(1) 공식 문서
- 스프링 공식 문서
- 스프링 부트는 같은 애플리케이션 코드를 유지하면서 다양한 외부 설정을 할 수 있도록 지원함
- 공식문서에서 나온 번호 순서대로 적용되어 동작 하고 번호가 높은 것이 우선 순위로 적용됨
(2) 자주 사용하는 우선 순위
- 아래로 갈수록 우선 순위가 높음
- 설정 데이터(application.properties)
- OS 환경변수
- 자바 시스템속성
- 커맨드 라인 옵션 인수
- @TestPropertySource (테스트에서 사용)
(3) 설정 데이터 우선 순위
- 아래로 갈수록 우선 순위가 높음
- jar 내부 application.properties
- jar 내부 프로필 적용 파일 application-{profile}.properties
- jar 외부 application.properties
- jar 외부 프로필 적용 파일 application-{profile}.properties
- 설정 데이터 우선 순위 스프링 공식 문서
(4) 우선 순위 이해 방법 - 복습
- 우선 순위는 상식 선에서 딱 2가지만 기억하면 됨
- 1. 더 유연한 것이 우선권을 가짐, (변경하기 어려운 파일 보다 실행시 원하는 값을 줄 수 있는 자바 시스템 속성이 더 우선권을 가짐)
- 2. 범위가 더 넓은 것보다 좁은 것이 우선권을 가짐, (자바 시스템 속성은 해당 JVM안에서 모두 접근할 수 있으나 커맨드 라인 옵션 인수는 main의 args를 통해서 들어오기 때문에 접근 범위가 더 좁음)
- 즉, 자바 시스템 속성과 커맨드 라인 옵션 인수의 경우 컴캔드 라인 옵션 인수의 범위가 더 좁기 때문에 커맨드 라인 옵션 인수가 우선권을 가짐
2) 추가 또는 변경되는 방식
(1) 실제 동작 방식과 개념 이해
- Environment를 통해서 조회하는 관점에서 보면 외부 설정값들은 계속 추가되거나 기존 값을 덮어서 변경하는 것처럼 보임
- 물론 실제 값을 덮어서 변경하는 것이 아니며 우선순위가 높은 값이 조회되는 것이지만 위에처럼 이해하면 개념적으로 더 쉽게 이해할 수 있음
(2) 개념 이해 예시
- 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가 적용됨
(3) 정리
- 우선 순위에 따라서 설정을 추가하거나 변경하는 방식은 상당히 편리하면서도 유연한 구조를 만들어줌
- 실무에서 대부분의 개발자들은 application.properties에 외부 설정값들을 보관하며 이렇게 설정 데이터를 기본으로 사용하다가 일부 속성을 변경할 필요가 있다면 더 높은 우선순위를 가지는 자바 시스템 속성이나 커맨드 라인 옵션 인수를 사용하면됨
- 또는 기본적으로 application.properties를 jar 내부에 내장하고 있다가 특별한 환경에서는 application.properties를 외부 파일로 새로 만들고 변경하고 싶은 일부 속성만 입력해서 변경하는 것도 가능함