Property Binding

스프링부트 프로젝트를 생성하면 외부 프로퍼티 설정파일을 만들게 된다.(application.yml, application.properties ...) 여기 정의한 키-값들이 결국 어플리케이션이 기동되면서 클래스로 바인딩 되는데 이 과정에 대해 알아본다.

 

먼저 클래스에서 외부 프로퍼티 값들을 가져오는 방식을 살펴보자. 아래 3가지를 이용할 수 있다.

  • @Value : 소스코드에서 바로 프로퍼티에 주입받아 사용
  • Environment : 외부설정이 바인딩 된 Environment 빈을 주입받아 사용
  • @ConfigurationProperties : 외부설정이 바인딩 될 Bean(객체)을 생성하여 사용

가장 빈번하게 사용하는 방식은 @Value("${property}") 방식일 듯 싶지만 불러올 프로퍼티의 갯수가 많거나, 계층형의 구조를 가지고 있는 경우 등에는 적절하지 않다. 또한 프로퍼티명이나 타입을 직접 입력해야 하므로 오류에 대한 여지도 있다. @Value와 Environment 빈에 대한 예시는 아래와 같다.

application.properties에 다음과 같이 프로퍼티 키-값 정의 : yangs.name=brand

@Component
public class SimpleRunner implements ApplicationRunner {
    @Autowired
    Environment environment; // Environment 타입의 빈 주입

    @Value("${yangs.name}") // ${}내부에 작성된 프로퍼티키에 해당하는 값을 세팅
    String name1; 

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("Value 애노테이션의 yangs.name : " + name1);

        String name2 = environment.getProperty("yangs.name");
        System.out.println("Environment bean의 yangs.name : " + name2);
    }
}

// 아래는 콘솔 출력결과
Value 애노테이션의 yangs.name : brand

Environment bean yangs.name : brand


간단한 몇 개의 값이라면 @Value를 사용하여 바인딩할 수 있지만, 검증이나 Type-safe, 계층형 구조 등을 처리할 수 있는 @ConfigurationProperties를 이용한 방법이 있다,

application.yml의 예시와 이 값을 @ConfigurationProperties를 어떻게 사용하여 바인딩 하는지 예제로 살펴본다.


application.yml 예시

일반적으로 프로퍼티명은 아래 is-main-tester처럼 소문자와 대쉬(-)를 구분자로 하는 명칭이 권장된다.
프로퍼티명 앞의 대쉬(-)는 리스트형을 나타낼때 사용한다.

test:
  name: tester
  cnt: 99
  is-main-tester: true
  sub-testers:
    - name: sub-tester1
       cnt: 11
       is-main-tester: false
    - name: sub-tester2
       cnt: 22
       is-main-tester: false


@ConfigurationProperties(prefix = “test”) 로 프로퍼티클래스에 값 바인딩하기

프로퍼티가 맵핑될 클래스(아래의 TesterProperties)를 작성하고 @ConfigurationProperties을 마킹한다. 이 클래스는 프로퍼티값이 맵핑될 클래스라는 의미로 애노테이션에는 프로퍼티명의 prefix(아래의 prefix = "test")를 지정할 수 있다.
프로퍼티 파일에서 리스트형으로 작성한 sub-testers를 맵핑할 수 있도록 List<Subster> subTesters 로 선언한 것을 볼 수 있고,
작성된 위의 프로퍼티 파일은 kebab case(- 가 구분자)로 작성하였으나 클래스 변수명은 camel case이다.
스프링부트에서는 프로퍼티명의 camel case, kebab case, underscore notation 간의 변환을 모두 지원한다.
subTesters - sub-testers - sub_testers 모두 변환하여 바인딩이 가능하다.

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

// 읽어들인 프로퍼티 중 해당 prefix의 프로퍼티값을 바인딩한다.
@ConfigurationProperties(prefix = "test")
public class TesterProperties {
    private String name;
    private int cnt;
    private boolean isMainTester;
    private List<SubTester> subTesters = new ArrayList<>();

    // Getter, Setter, ToString 생략
    
    static class SubTester {
        private String name;
        private int cnt;
        private boolean isMainTester;
            
        // Getter, Setter, ToString 생략
    }
}

@EnableConfigurationProperties(TesterProperties.class)로 프로퍼티클래스 사용하기

바인딩이 완료된 TesterProperties를 빈으로 등록하여 사용하기 위해 @EnableConfigurationProperties를 마킹한다.
주로 프로퍼티 값을 사용할 @Configuration 파일에 마킹하며, @ConfigurationProperties가 마킹된 클래스를 빈으로 등록해주는 역할이다. 만일 프로퍼티 클래스가 다수라면 @ConfigurationPropertiesScan({"base.package1", "base.package2"}) 으로 여러 패키지를 스캔하여 등록할 수도 있다.
프로퍼티가 제대로 바인딩 됬는지 테스트를 위해 ApplicationRunner를 작성 후 콘솔로그를 찍어본다.

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@EnableConfigurationProperties(TesterProperties.class)
public class PropertyRunner implements ApplicationRunner {
    private final TesterProperties testerProperties; // 값이 바인딩된 빈이 의존성 주입된다.

    public PropertyRunner(TesterProperties testerProperties) {
        this.testerProperties = testerProperties;
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        // 바인딩이 제대로 되었는지 콘솔 출력
        System.out.println("=================================");
        System.out.println("TesterProperties : " + testerProperties);
        System.out.println("=================================");
    }
}

=================================
TesterProperties : TesterProperties{name='tester', cnt=99, isMainTester=true, 
subTesters=[SubTester{name='sub-tester1', cnt=11, isMainTester=false}, 
                    SubTester{name='sub-tester2', cnt=22, isMainTester=false}]}
=================================

String, int, boolean 및 List 형까지 바인딩이 제대로 된 것을 확인할 수 있다. 혹여 바인딩시 Validation을 하고 싶다면 아래와 같이 스프링의 @Validated 와 JSR-303에 정의된 javax.validation 애노테이션을 사용할 수 있다.

@ConfigurationProperties(prefix = "test")
@Validated // 검증대상임을 표시
public class TesterProperties {
    @NotNull
    private String name;
    @Max(90)
    private int cnt;
    private boolean isMainTester;
    @Valid // 내부 프로퍼티에 검증이 적용되려면 @Valid를 마킹한다.
    private List<SubTester> subTesters = new ArrayList<>();

    static class SubTester {
        @NotNull
        private String name;
        @Min(20)
        private int cnt;
        private boolean isMainTester;
    }
    
    // Getter, Setter, ToString 생략
}

요약하면

  • 외부에서 설정된 프로퍼티 파일을 코드에서 사용하는 방법은 여러가지가 있다.
  • 만일 읽어올 프로퍼티가 많거나 계층형이라면 객체에 바인딩하여 사용하는 것을 고려하자.
  • 프로퍼티값을 객체에 바인딩하여 빈으로 등록 후 사용을 위해서는 @ConfigurationProperties(prefix = "test"), @EnableConfigurationProperties, @ConfigurationPropertiesScan({"base.package1", "base.package2"}) 같은 애노테이션을 조합한다.
  • 프로퍼티 바인딩시 검증을 위해서는 @Validated와 javax.validation 애노테이션을 사용한다.

+ Recent posts