Spring : 외부 설정이란?

    들어가기 전

    이 글은 김영한님의 인프런 강의를 복습하며 작성한 글입니다.

     


     

     

    외부 설정이란? 

    • 하나의 어플리케이션을 여러 환경에서 사용해야 할 때가 있음. (개발, Beta, Release, Rc Phase 등)
    • 각 환경마다 서로 다른 설정값을 사용해야하는 경우가 많음

    어떻게 이 문제를 해결할 수 있을까? 

    • 가장 단순한 방법 : 각각의 환경에 맞게 어플리케이션을 빌드. 
      • 이 방법은 좋은 방법이 아님.
        • 환경에 따라 빌드를 여러 번 해야함. (빌드 결과물이 다름. A-beta.jar, A-rc.jar )
        • 이 경우, 개발 환경에서 검증이 다 되었다고 생각할 수 있지만 A-rc.jar는 새로운 아티팩트이기 때문에 개발과 똑같이 동작하지 않을 수도 있음. 
      • 즉, 유연성이 떨어지는 방법
    • 주로 사용하는 방법 : 빌드는 한번만 하고, 필요한 설정값은 외부 설정값으로 주입.
      • 하나의 빌드 결과물을 이용하고, 설정값은 실행 시점에 외부 설정값으로 주입한다. 
      • 같은 빌드 결과물을 사용하기 때문에 운영 환경에서도 믿고 쓸 수 있고, 유연하게 처리할 수 있다. 

    <주로 사용하는 방법>

    유지보수하기 좋은 어플리케이션 개발의 기본 원칙은 변하는 것 / 변하지 않는 것을 분리하는 것이다. 각 환경에 따라 변하는 외부 설정값은 분리, 변하지 않는 코드 + 빌드 결과물은 유지하는 방법이다. 

     

     

     

    외부 설정할 수 있는 방법?

    필요한 설정값을 외부에서 어떻게 주입할 수 있을까? 

    외부 설정 방법

    • OS 환경 변수 : OS에서 지원하는 외부 설정. 해당 OS를 사용하는 모든 프로세스에서 함께 사용
    • 자바 시스템 속성 : 자바에서 지원하는 외부 설정. 해당 JVM 안에서 사용
    • 자바 커맨드 라인 인수 : 커맨드 라인에서 전달하는 외부 설정, 실행 시 'main(args)' 메서드에서 사용
    • 외부 파일(설정 데이터): 프로그램에서 외부 파일을 직접 읽어서 사용.
      • 어플리케이션에서 특정 위치의 파일을 읽도록 해둔다. (data/hello.txt)
      • 그리고 각 서버마다 해당 파일안에 다른 설정 정보를 남겨둔다.
        • 개발 서버 : 'hello.txt': 'url=dev.db.com'
        • 운영 서버 : 'hello.txt': 'url=prod.db.com'

     

     


    외부 설정 - OS 환경 변수

    • 조회 방법
      • 윈도우 : `set`
      • Mac, Linux : `printenv`
    • 어플리케이션에서 환경 변수를 읽는 방법을 공부해보자.
    public static void main(String[] args) {
        // 환경변수를 읽을 수 있음.
        Map<String, String> envMap = System.getenv();
        for (String key : envMap.keySet()) {
            log.info("env {}={}", key, System.getenv(key));
        }
    }
    • OS 환경 변수는 다른 프로그램에서도 사용할 수 있는 전역변수 같은 느낌이다.
    • 다른 프로그램에서도 동일한 환경 변수에 다른 값을 넣어서 사용할 수도 있다. → 문제 발생함.
    • 여러 프로그램에서 같은 환경변수를 사용하는 경우가 있을 수도 있으나, 자바 프로그램 안에서만 사용되는 외부 설정값이 필요할 때도 있다.

     


    외부 설정 - 자바 시스템 속성 → 불러오기 / 설정하기

    • 자바 시스템 속성(Java System Properties)는 실행된 JVM 안에서 접근 가능한 외부 설정이다. 추가로 자바가 내부에서 미리 설정해두고 사용하는 속성들도 있다. 
    • 자바 시스템 속성은 다음 형태로 사용한다.
      • 예시 ) java -Durl=dev -jar app.jar
      • `-D` VM 옵션을 통해서 key=value 형식으로 자바 시스템 속성을 줄 수 있음.
      • -D 옵션은 항상 -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)));
        }
    }
    • 입력된 자바 System Properties는 System.getProperty()를 이용해 조회할 수 있음. 

    • 인텔리제이에서 Modify options → add VM Options를 클릭한 후, -Durl 형식으로 자바 시스템 속성을 추가한다.
    // 자바 시스템 속성을 코드에서 불러오기
    String url = System.getProperty("url");
    String username = System.getProperty("username");
    String password = System.getProperty("password");
    • 이렇게 추가한 -D 옵션은 getProperty()를 이용해 자바 시스템 속성에서 불러올 수 있다.
    • Jar로 빌드되어 있는 경우 java -Durl=hello -jar app.jar 같은 형식으로 할 수 있다. 
    // 코드 내에서 자바 시스템 속성 등록하기
    System.setProperty("HelloKey", "HelloValue");
    log.info("HelloKey = {}", System.getProperty("HelloKey"));

    코드 내에서 자바 시스템 속성을 등록하는 방법도 있다. 

     


    외부 설정 - 커맨드 라인 인수 (Key=Value 형식이 안됨)

    커맨드 라인 인수는 어플리케이션 실행 시점에 args로 값을 받음.

    • 다음과 같이 사용함. java -jar app.jar dataA dataB
    • 인텔리제이의 경우 Modify Options → Program Arguments를 이용하면 됨. 
    // 커맨드라인 옵션 읽어오기.
    public static void main(String[] args) {
        for (String arg : args) {
            log.info("arg = {}", arg);
        }
    }
    • 커맨드 라인 인수는 main() 함수의 args를 읽어오기만 하면 된다. 

     

    커맨드 라인 인수 Key=Value 형식 입력받으면?

    java -jar app.jar dev=db_user로 실행

    • 어플리케이션을 개발할 때는 보통 Key=Value 형식으로 하는 것이 편리하다. 
    • url=dev_db로 실행해보면 값이 통으로 들어오는 것을 볼 수 있다. 
    • 통으로 들어오는 값을 '='을 기준으로 파싱해서 Map 형태로 바꿔야하는 불편함이 있다.
      • 이 불편함을 스프링이 해결해줌.

     


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

    • 앞서서 커맨드라인에 key=value 형식으로 값을 주었을 경우, 하나의 문자열로 전달되어서 직접 파싱해야했다. 
    • 스프링에서는 커맨드라인 인수를 key=value 형식으로 편리하게 사용할 수 있도록 스프링 만의 표준 방식을 정의해서 지원해줌.
    • 스프링은 --key=value 형식으로 제공해주면 됨. 
      • DefaultApplicationArguments 객체를 생성하며, 받은 인수 args를 넘겨주면 됨. 
      • DefaultApplicationArguments에게 필요한 인수를 가져다 쓰면 됨.
      • --는 Optional 인수가 되고, --가 없는 경우는 통문자로 사용됨.
    // 프로그램 인수 --url=dev_db --username=dev_user mode=on
    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());
    
        // 짝대기 없어지고 Key 값만 남음.
        Set<String> optionNames = appArgs.getOptionNames();
        for (String optionName : optionNames) {
            log.info("option arg {}={}", optionName, appArgs.getOptionValues(optionName));
        }
    }

     

     


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

    • 스프링부트는 ApplicationArguments(커맨드라인 옵션 인수를 활용할 수 있는 객체)를 스프링 빈으로 등록해둠. 
    • 따라서 스프링부트 프로젝트에서는 ApplicationArguments 스프링 빈을 주입받으면 어디서든 설정값을 이용할 수 있다. 
    @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()));
            for (String optionName : arguments.getOptionNames()) {
                log.info("option Args {}={}", optionName, arguments.getOptionValues(optionName));
            }
        }
    }
    
    >>>>>>>>>>>>>>>
    // 옵션 --username=hello --password=ballo
    // 스프링부트 동작 시, 로그 확인됨.
    hello.CommandLineBean  : option Args password=[ballo]
    hello.CommandLineBean  : option Args username=[hello]

    위에서 ApplicationArguments를 주입받아서, 커맨드라인 인수가 정상적으로 주입된 것을 로그로 확인할 수 있다. 

     


    외부 설정 - 스프링 통합 (외부 환경 설정 읽는 방법의 추상화)

    • 대부분의 외부 환경 설정값은 key=value 형식으로 제공이 된다.
    • 그렇지만 OS 환경변수인지, JVM 시스템 속성인지, 커맨드라인 인자인지에 따라서 읽는 방법이 달라진다. 스프링은 이 부분을 추상화시켜준다. 
      • 시스템 OS : System.getEnv()
      • JVM : System.getProperty()
      • 커맨드라인 : args
    • 어느 순간 시스템 OS에서 값을 읽어오다가, 커맨드라인에서 값을 읽어오도록 정책이 바뀌는 경우가 있을 수 있다. 그럴 때 마다 외부설정 값을 읽어오는 코드를 모두 수정해야하는 불상사가 일어난다. 
    • 이런 문제를 해결하기 위해 스프링은 Environment, PropertySource라는 추상화를 통해서 해결한다.

     

    PropertySource 

    • 이 녀석은 추상 클래스다. 
    • 커맨드라인 옵션 인수, 자바 시스템 속성, OS 환경 변수, 설정 데이터 파일을 읽을 수 있는 PropertySource 추상 클래스를 구현한 클래스를 제공한다.
      • CommandLinePropertySource
    • 스프링은 로딩 시점에 필요한 PropertySource 들을 생성하고 Environment에서 사용할 수 있게 연결해둔다.
    • 설정데이터 파일 (application.properties, application.yaml)도 PropertySource에 추가된다.

     

    Environment

    • Environment를 통해서 특정 외부 설정에 종속되지 않고, 일관성있게 'Key=Value' 형식으로 외부 설정에 접근할 수 있게 해줌. 
    • Environment는 내부에서 여러 과정을 거쳐서 PropertySource들에 접근함. 
    • 같은 값이 있을 경우를 대비해 스프링은 미리 우선순위를 정해둠.
      • 우선순위는 상식 선에서 딱 2가지만 기억하면 됨. 
        • 더 유연한 것이 우선권을 가진다. (변경하기 어려운 파일보다 실행 시 원하는 값을 줄 수 있는 자바 시스템 속성이 우선권을 가짐)
        • 범위가 넓은 것보다 좁은 것이 우선권을 가짐. (자바 시스템 속성은 해당 JVM 안에서 모두 접근할 수 있음. 반면에 커맨드 라인 온셥 인수는 main의 arg를 통해서 들어오기 때문에 접근 범위가 더 좁다)
      • 커맨드 라인옵션 인수 > 자바 시스템 속성 인수
    • 모든 외부 설정은 이제 Environment를 통해서 조회하면 된다. 
    @Slf4j
    @Component
    @RequiredArgsConstructor
    public class EnvironmentCheck {
        private final Environment env;
        @PostConstruct
        public void init() {
            // 아래 환경 인수를 다 읽음. 
            // --url=dev --username=myuser --password=hello
            // -Durl=dev -Dusername=myuser -Dpassword=Hello
            log.info("url = {}, username = {}, password = {}",
                    env.getProperty("url"),
                    env.getProperty("username"),
                    env.getProperty("password"));
            
        }
    }

    예시 코드는 위와 같다.

     

     

     

     

     

    'Spring > Spring' 카테고리의 다른 글

    스프링 부트 - 자동 구성 관련 요약  (0) 2023.09.17
    스프링 부트 - 자동구성 4  (0) 2023.09.17
    스프링 부트 - 자동구성 2  (0) 2023.09.17
    스프링 부트 - 자동구성 3  (2) 2023.09.17
    스프링 부트 - 자동구성 1  (0) 2023.09.14

    댓글

    Designed by JB FACTORY