애플리케이션을 개발하다보면 모종의 이유로 외부 파일에 값을 저장하는 경우가 많습니다. 이번 글에서는 외부 파일에 저장된 값을 Spring에서 사용하기 위한 방법에 대해 알아보겠습니다.

이번 글은 Spring Boot 3, Java 17을 기준으로 작성되었으며, 참고 자료는 다음과 같습니다.

바인딩을 해야하는 이유

Untitled.png

앞서 말했듯 특정 상수들은 외부 파일에 값을 저장하는 경우가 많습니다. 보안상의 이유로, 혹은 잦은 변경 등 여러 이유로 인해 값을 따로 관리하기 위함인데요. Spring에서는 일반적으로 application properties 파일에 저장합니다. 저장된 값을 Spring에서 사용하기 위해선 값을 불러와 특정 변수에 대응시키는 바인딩 작업이 필요합니다.

@Value를 이용한 바인딩

외부 properties 값을 Spring에 바인딩하는 방법 중 가장 쉬운 방법은 @Value를 사용하는 것입니다.

@Component
public class ExternalClient {

    @Value("${external.key}")
    private String key;
}

@Component
public class ExternalClient {

    private final String key;

    public ExtenrnalClient(@Value("${external.key}") String key){
        this.key = key;
    }
}

문제점

위 방식은 외부 변수의 위치를 리터럴 문자열로 정의하고 있습니다. 따라서 외부 변수가 여러 곳에서 바인딩 되고 있다면 변수 이름을 바꿀 때 모든 곳의 리터럴을 변경해야한다는 문제점을 갖고 있습니다.

@ConfigurationProperties를 이용한 바인딩

위와 같은 문제를 해결하기 위해 Spring Boot에서는 @ConfigurationProperties를 이용한 바인딩을 지원합니다. 이를 이용하면 외부 변수를 한 곳에 묶어 클래스 단위로 관리할 수 있으며 객체를 Spring container에 Bean으로 등록하여 다른 Bean에 주입할 수 있습니다.

외부 변수 클래스 정의

다음과 같은 application property가 있다고 가정하겠습니다.

menu:
  breakfast: "국밥"
  lunch: "제육볶음"
  dinner: "돈까스"

이때, 변수들을 한 곳에 모을 클래스는 다음과 같이 정의할 수 있습니다.

@ConfigurationProperties(prefix = "menu")
public class MenuProperties {

    private String breakfast;
    private String lunch;
    private String dinner;
}

하지만 이렇게 정의하는 것은 그저 POJO 클래스를 정의한 것 뿐입니다. 실제로 외부 변수를 인스턴스화 하기 위해선 바인딩 과정이 필요합니다. 바인딩 방법에는 여러가지가 있습니다. 각 방법에 대해 자세히 알아봅시다.

@EnableConfigurationProperties

가장 쉬운 방법은 @EnableConfigurationProperties를 사용하는 것입니다. 일반적으로 main 클래스에 붙여 사용할 수 있습니다.

@EnableConfigurationProperties(MenuProperties.class)
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

그러나 Properties 클래스가 생길 때마다 해당 annotation을 업데이트 해야한다는 문제가 발생합니다.

Meta-annotation

또다른 방법은 @Configuration과 같은 Spring meta-annotation을 이용하는 것입니다.

@Configuration
@ConfigurationProperties(prefix = "menu")
public class MenuProperties {

    private String breakfast;
    private String lunch;
    private String dinner;

    // constructor, getter, setter
}

그러나 이 방법의 경우 인스턴스를 만들기 위해 setter가 필요합니다. 따라서 외부 변수를 가진 인스턴스가 언제든지 다른 값으로 변경될 수 있으며 이는 예상치 못한 결과를 낳을 수 있습니다.

@ConfigurationPropertiesScan

마지막 방법은 @ConfigurationPropertiesScan을 이용한 방법입니다. 이는 @ComponentScan과 비슷하게 동작합니다. 따라서 main 클래스에 다음과 같이 붙여 사용하면 하위 패키지에서 Properties 클래스를 찾아 Bean으로 등록합니다.

@SpringBootApplication
@ConfigurationPropertiesScan()
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

이 방법의 경우 getter와 setter를 사용하지 않아도 됩니다. 따라서 다음과 같이 final이나 record를 이용하여 외부 변수 객체를 불변 객체로 만들 수 있습니다.

@ConfigurationProperties(prefix = "menu")
public class MenuProperties {
    private final String breakfast;
    private final String lunch;
    private final String dinner;

    // @ConstructorBinding // 생성자가 두 개 이상인 경우 특정 생성자를 지정하기 위해 사용
    public AppProperties(String breakfast, String lunch, String dinner) {
        this.breakfast = breakfast;
        this.lunch = lunch;
        this.dinner = dinner;
    }
}
@ConfigurationProperties(prefix = "menu")
public record MenuProperties(String breakfast, String lunch, String dinner) {
}

마무리하며

이번 글에서는 Spring에서 외부 변수를 바인딩하여 사용할 때 외부 변수를 객체로 다룰 수 있는 @ConfigurationProperties에 대해 자세히 알아보았습니다. 그 중에서 @ConfigurationPropertiesScan 을 이용한 바인딩을 가장 추천드립니다. 불변 객체를 사용할 수 있어 안정적이며 새로운 클래스에 대한 코드 수정이 없어 확장성이 높기 때문입니다.

이 글에서 사용한 코드는 이 곳에서 확인하실 수 있습니다.

카테고리:

업데이트:

댓글남기기