스프링 외부 설정과 프로필(우선순위)

Coodori·2024년 1월 31일
0

Study

목록 보기
8/10

문제 상황

스프링에는 동적으로 환경을 바꿔야하는 경우가 있다 ex) 운영환경, 개발환경의 데이터소스 url,개발 ID, Password
가장 좋은 방법은 직접 정적인 파일에 정보를 넣는 것이겠지만

  1. 환경에 따라 빌드를 여러번 해야한다
  2. 빌드 결과물이 달라서 검증이 불가능하다.
  3. 유연성이 떨어진다.

그렇다면 우리가 변경되는 값을 외부 설정으로 주입해주면 안정성을 보장할 수 있다.

유지 보수하기 좋은 애플리케이션 개발의 가장 기본 원칙은 변하는 것과 않는 것을 분리하는 것이다.

외부 설정

  1. OS 환경변수
  2. 자바 시스템 속성
  3. 자바 커맨드 라인 인수
  4. 외부 파일

총 4가지의 외부 설정이 해당 빌드된 프로젝트에 영향을 미치게된다.

OS 환경변수

printenv로 확인 가능한 해당 OS를 사용하는 모든 프로그램에서 읽을 수 있는 설정값

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()로 설정을 가져올 수 있다
DBURL 등 지정이 가능하지만 단점은 이 프로그램뿐만 아니라 다른 프로그램에서도 해당 변수값이 사용가능하여 중복이 될 수 있다.

자바 시스템 속성

JVM 안에서 접근 가능한 외부 설정

사용법
java -Durl=dev -jar app.jar

스프링부트 조회 방법

public static void main(String[] args) {
 Properties properties = System.getProperties();
 for (Object key : properties.keySet()) {
 log.info("prop {}={}", key, 
System.getProperty(String.valueOf(key)));
 }

key = value 형식으로 받을 수 있다.

동적으로 JVM위에서 빌드된 파일을 실행 시킬때 -D옵션을 통해 속성을 전달 가능하고 자바 코드 내부에서도 추가가 가능하다.
: System.setProperty(propertyName, "propertyValue")
하지만 아래 방법은 코드에 박히는 것이라 장점을 크게 체감 할 수없다.

커맨드 라인 인수

애플리케이션 실행 시점에 외부 설정 값을 main(args) 메서드의 args 파라미터로 전달

public static void main(String[] args) {
 for (String arg : args) {
 	log.info("arg {}", arg);
 }
 }

커맨드 라인 인수는 공백으로 구분을 하며 Program argument 에 data1 data2 순으로 넣으면 된다.

직접 실행 할때는 $ java -jar app.jar data1 data2
(data1, data2) 가 전달된다.

하지만 이러한 데이터 전달은 데이터가 어떤 것을 뜻하는지 모르고 보통 애플리케이션을 개발할때는 key = value 형태로 활용이 가능하도록 개발을 한다.

그렇다면 $ java -jar app.jar A=data1 B=data2로 보낸다면?
("A=data1","B=data2")로 총 두개의 문자열이 오게된다.( 공백을 연결하려면 ""를 사용하면 된다.)

하지만 우리가 원하는 것은 A = data1로 사용하고 싶은것이다.....

커맨드 라인 옵션 인수

마구잡이로 =를 파싱하면 그것 요구사항에 따라 문제가 생길 수도 있다.
그래서 가이드라인이 잡혀있다.

-- 를 두개 적고 000=1111을 쓰면 key = value 로 분리를 시켜준다.
$ java -jar app.jar --username=userA --username=userB

사용 방법은

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);
 log.info("mode={}", mode);
 }

ApplicationArguments appArgs = new DefaultApplicationArguments(args); 으로 커맨드 라인 옵션 인수를 받아서 일관된 가이드라인으로 key = value 가능하다.

하나의 키에 여러값을 포함할 수 있기때문에 appArgs.getOptionValues(key)의 반환자료형은 LIST다
커맨드 라인 옵션인수는 자바 기능이 아니고 스프링이 제공하는 기술

커맨드 라인 옵션 인수와 스프링 부트

동일하게 사용가능하다

ApplicationArguments를 스프링 빈으로 등록해서 커맨드 라인을 저장해 놓고 어디서든 주입을 받아서 사용 가능하다는 장점이 있다. 위의args와 다름

처음 component 스캔 당시에 주입이 되고 선조립이 되어 초기화후 메소드 실행

@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 형식값을 제공한다.

이렇게 되면 외부에서 다양하게 조작이 가능하다.

  • PropertySource 라는 추상 클래스를 제공하고 각 구현체를 만들어놓았다.

  • 스프링은 로딩 시점에 필요한 PropertySource 생성 후 Environment 사용

  • Environment 를 통해 특정 외부 설정에 종속되지 않는다.

  • environment.getProperty(key)를 통해 접근

  • yml, properties도 모두 추가된다.

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

스프링의 우선 순위

  • 더 유연한 것이 우선권을 가진다. (파일 < 자바 시스템)
  • 범위가 넓은 것 보다 좁은 것이 우선권을 가진다. (자바 시스템 < 커맨드 라인 옵션 인수- main)

스프링 프로필

내부 파일 분리

spring.profiles.active를 외부 설정에 넣으면 해당 프로필을 사용한다고 판단
그러면 해당 프로필의 정보로 바꿔줘야하는데

application-{profile}.properties 를 읽어온다.

커맨드 라인 인수로는 --spring.profiles.active=dev 로 넣어줄 수도 있다.

하지만 해당 방법으로 파일을 여러개 만들었을 경우 한눈에 들어오지 않는 단점이 존재한다.

스프링 프로필 내부 파일 합체

물리적인 하나의 파일을 논리적으로 영역을 구분한다.

이렇게 논리적으로 구분이 가능하고 기본 application-dev.properties와 동일하다

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

주의! 속성파일 구분 기호에는 선행 공백이 없어야하고 --- 3개 필수
주석이 구분자 위아래로 있으면 안됨
yml은 위에서 아래로 우선순위가 높은 값을 읽는다.
기본 프로필은 default

외부설정 우선순위 총 정리

숫자가 높을 수록 우선순위가 높다

  1. 설정 데이터( application.properties )
  2. OS 환경변수
  3. 자바 시스템 속성
  4. 커맨드 라인 옵션 인수
  5. @TestPropertySource (테스트에서 사용)

설정 데이터 우선순위
1. jar 내부 application.properties
2. jar 내부 프로필 적용 파일 application-{profile}.properties
3. jar 외부 application.properties
4. jar 외부 프로필 적용 파일 application-{profile}.properties

결론

해당 방법을 활용하여 환경을 분리할 수 있으며 직접적인 파일을 손대지 않고 테스트 검증이 완료된 빌드 파일을 활용한다.

중간의 실수의 불안함을 제거하고 동일한 환경에서 설정을 바꿔서 실행 할 수 있다.

운영환경에서 문제가 생길 경우 크리티컬한 문제가 생기니 개발 환경의 환경 변수를 입력하여 프로필을 조절하고 해당 변수로 선 개발을 한뒤 실행시에 외부 활성 프로필 값으로 넣어주자!

또한 민감정보를 직접 파일로 넣는것은 유출 위험이 있으니 변수처리를 해서 입력을 해주자(이 또한 안전하게 실행해야겠지만....)

profile
https://coodori.notion.site/0b6587977c104158be520995523b7640

0개의 댓글