Notice
Recent Posts
Recent Comments
Link
관리 메뉴

look-forest

외부 설정 사용 본문

Spring/Spring boot - 핵심 원리와 활용

외부 설정 사용

studyHub 2025. 1. 30. 19:57

스프링이 제공하는 외부 설정을 읽는 방법들을 알아보자.

 

스프링이 지원하는 다양한 외부 설정 조회 방법

  • Environment
  • @Value - 값 주입
  • @ConfigurationProperties - 타입 안전한 설정 속성

외부 설정 사용 - Environment

외부 설정들은 스프링이 제공하는 Environment 를 통해서 일관된 방식으로 조회할 수 있다.

@Configuration
public class MyDataSourceEnvConfig {

    private final Environment 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 를 직접 주입받고, env.getProperty(key) 를 통해서 값을 꺼내는 과정을 반복해야 한다는 점이다. 스프링은 @Value 를 통해서 외부 설정값을 주입 받는 더욱 편리한 기능을 제공한다.

 


외부설정 사용 - @Value

@Value 를 사용하면 외부 설정값을 편리하게 주입받을 수 있다.

참고로 @Value 도 내부에서는 Environment 를 사용한다.

@Value 는 필드에 사용할 수도 있고, 파라미터에 사용할 수도 있다. 타입 컨버팅도 적용된다.

@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:1}") 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 로 하나하나 외부 설정 정보의 키 값을 입력받고, 주입 받아와야 하는 부분이 번거롭다.

그리고 설정 데이터를 보면 하나하나 분리되어 있는 것이 아니라 my.datasource 의 묶음으로 되어 있다.

이런 부분을 객체로 변환해서 편리하게 사용할 수 있는 방법을 알아보자.

 


외부설정 사용 - @ConfigurationProperties

Type-safe Configuration Properties

스프링은 외부 설정의 묶음 정보를 객체로 변환하는 기능을 제공한다. 이것을 타입 안전한 설정 속성이라 한다. 객체를 사용하면 타입을 사용할 수 있다. 따라서 실수로 잘못된 타입이 들어오는 문제도 방지할 수 있고, 객체를 통해서 활용할 수 있는 부분들이 많아진다. 쉽게 이야기해서 외부 설정을 자바 코드로 관리할 수 있는 것이다. 그리고 설정 정보 그 자체도 타입을 가지게 된다.

 

ConfigurationProperties 장점

  • 외부 설정을 객체로 편리하게 변환해서 사용할 수 있다.
  • 외부 설정의 계층을 객체로 편리하게 표현할 수 있다.
  • 외부 설정을 타입 안전하게 사용할 수 있다.
  • 검증기를 적용할 수 있다.

 

ConfigurationProperties 사용법

1. 외부 설정을 주입 받을 객체를 생성

@ConfigurationProperties 이 있으면 외부 설정을 주입 받는 객체라는 뜻이다. <지정>

기본 주입 방식은 자바빈 프로퍼티 방식이다. Getter , Setter 가 필요하다.

설정 속성은 빈으로 등록된다.

@Data //getter, setter
@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 = new ArrayList<>();
    }
}

 

2. 설정 속성 사용

Spring Boot 2.2+에서는 @Component, @Configuration, @Service 등의 어노테이션이 붙은 클래스에서 @ConfigurationProperties를 사용할 때 자동 감지된다!

 

Spring Boot 3.0 이전: @ConfigurationProperties에 @Component를 붙이면 자동 빈 등록된다.
⚠️ Spring Boot 3.0 이상 (3.5 포함): @Component 방식은 권장되지 않고, 일부 경우 제대로 작동하지 않을 수 있다.

Spring Boot 3에서는 @ConfigurationProperties를 명확히 등록 방식에 따라 구분해서 사용하라고 가이드.

# 2.2-

@EnableConfigurationProperties 를 붙여야 설정 값을 주입 받는다. <주입>

해당 클래스는 빈으로 등록된다.

@EnableConfigurationProperties(MyDataSourcePropertiesV1.class)
public class MyDataSourceConfigV1 {

    private final MyDataSourcePropertiesV1 properties;

    @Bean
    public MyDataSource myDataSource() {
        return new MyDataSource(
                properties.getUrl(),
                properties.getUsername(),
                properties.getPassword(),
                properties.getEtc().getMaxConnection(),
                properties.getEtc().getTimeout(),
                properties.getEtc().getOptions());
    }
}

 

 

@ConfigurationPropertiesScan

@ConfigurationProperties 를 하나하나 직접 등록할 때는 @EnableConfigurationProperties 를 사용한다. @ConfigurationProperties 를 특정 범위로 자동 등록할 때는 @ConfigurationPropertiesScan 을 사용하면 된다.

@SpringBootApplication(scanBasePackages = "hello.datasource")
@ConfigurationPropertiesScan
public class ExternalReadApplication {

    public static void main(String[] args) {
        SpringApplication.run(ExternalReadApplication.class, args);
    }

}

 

 

 

문제

MyDataSourcePropertiesV1 은 스프링 빈으로 등록된다. 그런데 Setter 를 가지고 있기 때문에 누군가 실수로 값을 변경하는 문제가 발생할 수 있다. 여기에 있는 값들은 외부 설정값을 사용해서 초기에만 설정되고, 이후에는 변경하면 안된다. 이럴 때 Setter 를 제거하고 대신에 생성자를 사용하면 중간에 데이터를 변경하는 실수를 근본적으로 방지할 수 있다.

 

 

외부설정 사용 - @ConfigurationProperties 생성자

@ConfigurationProperties 는 Setter를 사용하는 자바빈 프로퍼티 방식이 아니라 생성자를 통해서 안전하게 만들수 있다.

생성자 파라미터에 @DefaultValue를 붙이면 해당 값을 찾을 수 없는 경우 기본값을 사용한다.

@Getter
@ConfigurationProperties("my.datasource") //외부 설정을 주입받는 객체라는 뜻
public class MyDataSourcePropertiesV2 {
    private String url;
    private String username;
    private String password;
    private Etc etc;

//    @ConstructorBinding 생성자가 하나일 때는 생략할 수 있다. 생성자가 둘 이상인 경우에는 생성자 바인딩을 사용할 생성자에 적용
    public MyDataSourcePropertiesV2(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 {
        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;
        }
    }
}

 

 

외부설정 사용 - @ConfigurationProperties 검증

@ConfigurationProperties 를 통해서 숫자가 들어가야 하는 부분에 문자가 입력되는 문제와 같은 타입이 맞지 않는 데이터를 입력하는 문제는 예방할 수 있다. 그런데 문제는 숫자의 범위라던가, 문자의 길이 같은 부분은 검증이 어렵다. 예를 들어서 최대 커넥션 숫자는 최소 1 최대 999 라는 범위를 가져야 한다면 어떻게 검증할 수 있을까? 이메일을 외부 설정에 입력했는데, 만약 이메일 형식에 맞지 않는다면 어떻게 검증할 수 있을까? 개발자가 직접 하나하나 검증 코드를 작성해도 되지만, 자바에는 자바 빈 검증기(java bean validation)이라는 훌륭한 표준 검증기가 제공된다.

 

@ConfigurationProperties 은 자바 객체이기 때문에 스프링이 자바 빈 검증기를 사용할 수 있도록 지원한다.

자바 빈 검증기를 사용하려면 spring-boot-starter-validation 이 필요하다.

클래스에 @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;
        }
    }
}

 

※ 아래와 같이 프로퍼티 값을 잘못 넣으면 애플리케이션 로딩 시점에 다음과 같은 오류 메시지를 확인 할 수 있다
Property: my.datasource.etc.maxConnection
Value: "0"
Origin: class path resource [application.properties] - 4:34
Reason: 1 이상이어야 합니다

 


YAML

스프링은 설정 데이터를 사용할 때 application.properties 뿐만 아니라 application.yml 이라는 형식도 지원한다.

YAML(YAML Ain't Markup Language)은 사람이 읽기 좋은 데이터 구조를 목표로 한다. 확장자는 yaml , yml 이다.

YML에도 --- 프로필을 적용할 수 있다.

my:
  datasource:
    url: local.db.com
    username: local_user
    password: local_pw
    etc:
      max-connection: 1
      timeout: 60s
      options: LOCAL, CACHE
---
spring:
  config:
    activate:
      on-profile: dev
my:
  datasource:
    url: dev.db.com
    username: dev_user
    password: dev_pw
    etc:
      max-connection: 10
      timeout: 60s
      options: DEV, CACHE

 


@Profile

프로필과 외부 설정을 사용해서 각 환경마다 설정값을 다르게 적용하는 것은 이해했다. 그런데 설정값이 다른 정도가 아니라 각 환경마다 서로 다른 빈을 등록해야 한다면 어떻게 해야할까? 

@Profile 을 사용하면 각 환경 별로 외부 설정 값을 분리하는 것을 넘어서, 등록되는 스프링 빈도 분리할 수 있다

 

@Profile 애노테이션을 사용하면 해당 프로필이 활성화된 경우에만 빈을 등록한다.

특정 조건에 따라서 해당 빈을 등록할지 말지 선택한다. @Conditional 기능을 활용해서 @Profile 기능을 제공하는 것이다.

코드를 보면 @Conditional(ProfileCondition.class)를 확인할 수 있다.

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

 

 


참고 자료 & 이미지 출처
스프링 부트 - 핵심 원리와 활용 (김영한 님)