본문 바로가기
Spring

스프링 부트에서 외부 설정에 접근하는 방법

by hseong 2023. 5. 29.

0.

 

스프링 부트 - 핵심 원리와 활용 - 인프런 | 강의

실무에 필요한 스프링 부트는 이 강의 하나로 모두 정리해드립니다., - 강의 소개 | 인프런

www.inflearn.com

본 게시글은 해당 강의를 수강하고 정리한 내용입니다.

 

1. 외부 설정을 위한 4가지 방법

스프링 애플리케이션에 외부 설정을 위한 방법으로는 다음 4가지가 있다.

  • OS 환경 변수
    • 해당 OS를 사용하는 모든 프로그램에서 읽을 수 있는 설정값
    • 대표적으로 JAVA_HOME 이 있다.
  • 자바 시스템 속성
    • JVM 안에서 접근 가능한 외부 설정
    • 자바 프로그램을 실행할때 사용한다.
    java -Durl=dev -jar app.jar
    
    • -D VM 옵션을 통해서 key=value 형식으로 전달한다. 이때 -D 옵션은 -jar 보다 앞에 위치해야 한다.
  • 자바 커맨드 라인 인수
    • 애플리케이션 실행 시점에 외부 설정값을 main(args) 메서드의 args 파라미터로 전달하는 방법이다.
    java -jar app.jar datA dataB
    
    • 필요한 데이터를 마지막 위치에 공백으로 구분해서 전달한다.
  • 외부 파일
    • .properties라는 파일을 통해 key=value 형식으로 설정값을 관리할 수 있다.

 

2. 커맨드 라인 옵션 인수

스프링은 커맨드 라인 인수를 key=value 형식으로 편리하게 사용할 수 있는 방법을 제공한다.

커맨드 라인에 --(dash 2개)를 입력하고 key=value 형식으로 전달하는 것을 커맨드 라인 옵션 인수라 한다.

--key=value --url=dev

스프링 부트는 커맨드 라인 옵션 인수를 활용할 수 있는 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()));
        log.info("optionNames {}", List.of(arguments.getOptionNames()));
        Set<String> optionNames = arguments.getOptionNames();
        for (String optionName : optionNames) {
            log.info("option args {}={}", optionName, arguments.getOptionValues(optionName));
        }
    }
}

 

 

3. 스프링 통합

스프링은 OS 환경변수, 자바 시스템 속성, 커맨드 라인 옵션 인수, 설정 파일에 대한 접근을 추상화한 Environment와 PropertySource를 제공한다.

PropertySource

  • 스프링은 PropertySource라는 추상 클래스를 제공하고 각각의 외부 설정을 조회하는 xxxPropertySource 구현체를 만들어두었다.
  • 스프링은 로딩 시점에 필요한 PropertySource들을 생성하고, Environment에서 사용할 수 있게 연결해둔다.

Environment

  • Environment를 통해 특정 외부 설정에 종속되지 않고, 일관성 있게 key=value 형식의 외부 설정에 접근할 수 있다.
  • environment.getProperty(key)를 통해서 값을 조회할 수 있다.
  • key가 중복될 경우 스프링은 미리 정해진 우선순위에 따라 처리한다.

 

4. application.properties

외부 설정 파일

스프링 부트는 개발자가 따로 설정하지 않아도 자동으로 설정 파일을 읽어들일수 있도록 이미 구현되어 있다. 개발자가 application.properties 라는 이름의 파일을 자바를 실행하는 위치에 만들어두면, 스프링이 해당 파일을 읽어서 사용할 수 있는 PropertySource의 구현체를 제공한다.

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

application.properties

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

 

내부 설정 파일

외부에 설정 파일을 두지 않고 프로젝트 내부에 포함하여 관리할 수 있다.

스프링은 외부 설정으로 넘어온 프로필 값에 따라 각각 다른 파일을 읽어들일수 있는 방법에 대해 구현해두었다.

main/resources/application-dev.properties

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

main/resources/application-prod.properties

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

개발자는 실행 시점에 spring.profiles.active 외부 설정에 값을 넣으면 스프링은 해당 프로필을 사용한다고 판단한다. 그리고 프로필에 따라서 다음과 같은 규칙으로 해당 프로필에 맞는 내부 설정 파일을 조회한다.

appication-{profile}.properties
#자바 시스템 속성 실행
java -Dspring.profiles.active=dev -jar app.jar
#커맨드 라인 옵션 인수 실행
java -jar app.jar --spring.profiles.active=dev

 

단일 설정 파일

스프링은 여러개의 설정 파일은 하나의 파일 안에서 사용할수 있도록 논리적으로 영역을 구분하는 방법을 제공한다.

main/resources/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
  • application.properties 라는 하나의 파일 안에서 논리적으로 영역을 나눌 수 있다.
  • application.proerties 는 #--- 또는 !---(dash 3개)
  • application.yml 은- --(dash 3개)
  • 각각의 설정 데이터는 spring.config.activate.on-profile 에 값을 지정하여 활성화한다.
  • 단, 설정 데이터 구분 전후로 주석을 사용해서는 안된다.
  • 프로필을 지정하지 않는다면 스프링은 프로필이 지정되지 않은 값을 기본값을 사용한다.

 

5. 스프링이 지원하는 외부 설정 조회 방법

Environment

@Slf4j
@Configuration
public class MyDataSourceEnvConfig {

    private final Environment env;

    public MyDataSourceEnvConfig(Environment env) {
        this.env = env;
    }

    @Bean
    public MyDataSource myDataSource() {
        String url = env.getProperty("my.datasource.url");
        String username = env.getProperty("my.datasource.username");
        String password = env.getProperty("my.datasource.password");
        int maxConnection = env.getProperty("my.datasource.etc.max-connection", Integer.class);
        Duration timeout = env.getProperty("my.datasource.etc.timeout", Duration.class);
        List<String> options = env.getProperty("my.datasource.etc.options", List.class);

        return new MyDataSource(url, username, password, maxConnection, timeout, options);
    }
}
  • Environment를 사용하면 외부 설정의 종류와 관계없이 코드 안에서 일관성 있게 외부 설정을 조회할 수 있다.
  • Environment.getPropery(key, Type)를 호출할 때 타입정보를 주면 해당 타입으로 변환해준다.

 

@Value

@Slf4j
@Configuration
public class MyDataSourceValueConfig {

    @Value("${my.datasource.url}")
    private String url;
    @Value("${my.datasource.username}")
    private String username;
    @Value("${my.datasource.password}")
    private String password;
    @Value("${my.datasource.etc.max-connection}")
    private int maxConnection;
    @Value("${my.datasource.etc.timeout}")
    private Duration timeout;
    @Value("${my.datasource.etc.options}")
    private List<String> options;

    @Bean
    public MyDataSource myDataSource1() {
        return new MyDataSource(url, username, password, maxConnection, timeout, options);
    }

    @Bean
    public MyDataSource myDataSource2(
            @Value("${my.datasource.url}") String url,
            @Value("${my.datasource.username}") String username,
            @Value("${my.datasource.password}") String password,
            @Value("${my.datasource.etc.max-connection}") int maxConnection,
            @Value("${my.datasource.etc.timeout}") Duration timeout,
            @Value("${my.datasource.etc.options}") List<String> options) {

        return new MyDataSource(url, username, password, maxConnection, timeout, options);
    }
}
  • @Value에 ${}를 사용하여 외부 설정의 키 값을 주면 원하는 값을 주입 받을 수 있다.
  • 필드에도 사용할 수 있고 파라미터에도 사용할 수 있다.
  • @Value(”{my.datasource.etc.max-connection:1}” 과 같이 키를 찾지 못할 경우 : 뒤에 값을 명시하여 기본값을 사용할 수 있다.

 

@ConfigurationProperties

Type-safe Configuration Properties

스프링은 외부 설정의 묶음 정보를 객체로 변환하는 기능을 제공한다. 외부 설정을 자바 코드로 관리함으로써 타입 안전한 설정 속성을 가질 수 있다.

@Data
@ConfigurationProperties("my.datasource")
public class MyDataSourcePropertiesV1 {

    private String url;
    private String username;
    private String password;
    private Etc etc;

    @Data
    public static class Etc {
        private int maxConnection;
        private Duration timeout;
        private List<String> options;
    }
}
  • 외부 설정을 주입 받을 객체를 생성한다.
  • @ConfigurationProperties 이 있으면 외부 설정을 주입 받는 객체라는 뜻이다. 여기에 외부 설정 key 묶음의 시작점인 my.datasource 를 적어준다.
  • 기본 주입 방식은 자바빈 프로퍼티 방식이다. Getter, Setter가 필요하다.
@Slf4j
@EnableConfigurationProperties(MyDataSourcePropertiesV1.class)
public class MyDataSourceConfigV1 {

    private final MyDataSourcePropertiesV1 properties;

    public MyDataSourceConfigV1(MyDataSourcePropertiesV1 properties) {
        this.properties = properties;
    }

    @Bean
    public MyDataSource dataSource() {
        return new MyDataSource(
                properties.getUrl(),
                properties.getUsername(),
                properties.getPassword(),
                properties.getEtc().getMaxConnection(),
                properties.getEtc().getTimeout(),
                properties.getEtc().getOptions());
    }
}
  • @EnableConfigurationProperties(MyDataSourcePropertiesV1.class)
    • 스프링에게 사용할 @ConfigurationProperties를 지정해준다. 지정한 클래스는 스프링 빈으로 등록되고, 필요한 곳에서 주입 받아 사용할 수 있다.
  • @ConfigurationPropertiesScan
    • 특정 범위의 @CofigurationProperties를 자동 등록할 때 사용할 수 있다.
@Getter
@ConfigurationProperties("my.datasource")
public class MyDataSourcePropertiesV2 {

    private String url;
    private String username;
    private String password;
    private Etc etc;

    public MyDataSourcePropertiesV2(String url, String username, String password, @DefaultValue Etc etc) {
        this.url = url;
        this.username = username;
        this.password = password;
        this.etc = etc;
    }

    @Getter
    public static class Etc {
        private int maxConnection;
        private Duration timeout;
        private List<String> options;

        public Etc(int maxConnection, Duration timeout, @DefaultValue("DEFAULT") List<String> options) {
            this.maxConnection = maxConnection;
            this.timeout = timeout;
            this.options = options;
        }
    }
}
  • 자바 빈 프로퍼티 방식 대신 생성자를 이용할 수도 있다.
  • @DefaultValue
    • 해당 값을 찾을 수 없는 경우 기본값을 사용한다.
  • @DefaultValue Etc etc
    • etc를 찾을 수 없는 경우 Etc 객체를 생성하고 배부에 들어가는 값은 비워둔다.
  • @DefaultValue(”DEFAULT”) List<String> options
    • options를 찾을 수 없는 경우 DEFAULT라는 이름의 값을 사용한다.

 

@ConfigurationProperties 검증하기

@ConfigurationProperties는 자바 객체이기 때문에 스프링이 자바 빈 검증기를 사용할 수 있도록 지원한다. 이때 검증을 수행하는 클래스에는 @Validated를 추가하여 검증을 수행함을 알려야 한다.

@Getter
@ConfigurationProperties("my.datasource")
@Validated
public class MyDataSourcePropertiesV3 {

    @NotEmpty
    private String url;
    @NotEmpty
    private String username;
    @NotEmpty
    private String password;
    private Etc etc;

    public MyDataSourcePropertiesV3(String url, String username, String password, Etc etc) {
        this.url = url;
        this.username = username;
        this.password = password;
        this.etc = etc;
    }

    @Getter
    public static class Etc {
        @Min(1)
        @Max(999)
        private int maxConnection;
        @DurationMin(seconds = 1)
        @DurationMax(seconds = 60)
        private Duration timeout;
        private List<String> options;

        public Etc(int maxConnection, Duration timeout, @DefaultValue("DEFAULT") List<String> options) {
            this.maxConnection = maxConnection;
            this.timeout = timeout;
            this.options = options;
        }
    }
}

 

 

6. @Profile

스프링은 @Profile 애노테이션을 프로필에 따른 서로 다른 빈의 등록 또한 지원한다. 이를 통해 환경에 변경에 따른 코드의 변경을 최소화할 수 있다.

@Slf4j
@Configuration
public class PayConfig {

    @Bean
    @Profile("default")
    public LocalPayClient localPayClient() {
    	log.info("LocalPayClient 빈 등록");
        return new LocalPayClient();
    }

    @Bean
    @Profile("prod")
    public ProdPayClient prodPayClient() {
        log.info("ProdPayClient 빈 등록");
        return new ProdPayClient();
    }
}
  • @Profile 애노테이션은 내부에서 @Conditional을 이용하여 특정 조건에서만 해당 빈이 등록되도록 함으로써 이를 구현하였다.