개발/java

Spring 에서 configuration 을 다루는 방법 - @Value vs @ConfigurationProperties

ebang 2024. 2. 26. 22:04
반응형

사이드 프로젝트를 하면서 동료 개발자분께서 Components/KakaoKey 의 존재 이유를 여쭤보셨다.  application.properties 의 값을 @Value 어노테이션을 이용해 가져와 저장하는 목적의 클래스들이었다.

 

단순히 환경 설정을 위한 값을 저장하는 빈, 컴포넌트로는 사용하기 애매할 수 있다는 생각이 들었고, @ConfigurationProperties라는 어노테이션에 대해 알게 되어 이번 기회에 정리해보게 되었다. 

 

 

 Externalized Configuration

서로 다른 환경에서 애플리케이션이 돌아갈 수 있게 하는 방법 중 하나는 externalized configuration 이다. 

여러가지 외부 설정을 java properies 파일, yaml 파일, 환경 변수, command-line argument등을 통해 사용할 수 있다.

 

나처럼 여러가지 oauth 소셜 로그인을 위해서 설정 정보를 저장하기도 하고, 다양한 환경에서 구동될 수 있도록 하기 위해서 따로 빼놓는 값

들을 저장한다고 보면 될 것 같다. 

 

기본적으로 spring에서 propertysource 값이 오버라이딩되는 순서는 다음과 같다. 

3번에 application.properties 파일이 있는 것을 확인할 수 있다. 꽤나 높은 순위이다. 

 

 

 

그리고 설정 파일이 고려되는 순서는 다음과 같다. 

1순위로 고려되는 것이  applicaton.properties 이며, 이후에 'profile' 이 추가되었거나, 외부의 application.properties 파일들이 후순위로 고려된다고 이해하면 된다. 

 

 

 

 

application.properties 를 찾고 로딩하는 경로와 순서는 위와 같다. 

클래스 패스 루트경로에서부터,  /config 패키지에서 찾고, 현재 디렉토리내에서 config/ 내의 서브 디렉토리 순으로 찾아본다. 

 

클래스페스는 intelllij 기준으로 File/project Structure/Modules 에서 확인가능한데

나의 경우, 프로젝트 루트 경로가 클래스 패스였고, 

이와 같은 디렉토리 구성을 가지고 있으므로 클래스패스의 루트에서 찾다가 resources/application.properties 에서 찾게될 것이다. 

이렇게 찾은 파일은 spring 'Environment' 에 'PropertySources' 로 추가된다. 

 

추가적인 정보로, jar파일 실행 시 spring.config.name 옵션으로 'application' 대신 다른 이름으로 설정파일을 설정할 수 있고, 

원하는 클래스 패스를 설정하되 설정파일이 존재하지 않을 수도 있는 경우, optional  prefix 를 함께 사용할 수 있다. 이외에도 wildcard location, profile specific file 등 세부적인 내용을 많은데, 여기서 주로 다루는 내용은 아니므로 패스하도록 하겠다. 

 

 

결국 코드 단에서 값을 가져오기 위해서는,  두가지 방법을 통해 가져올 수 있다. 

 

  • @Value 어노테이션
  • @ConfigurationProperties 어노테이션

 

여기서는 두가지 방법의 차이를 비교해보려고 한다.  (공식문서)

공식문서를 통해서 확인해본 둘의 차이는 다음과 같다. 

  1. Relaxed binding
    • property 단어를 매치할 때 반드시 같은 단어가 아니더라도 camelCase, Kebab case, upper case, Underscore notation 등에 대해 유연하게 매치해준다.
    • ex) first-name, firstName, first_name, FIRSTNAME
    • @ConfigurationProperties는 지원하는 반면 @Value는 제한적으로 지원한다.
  2. Meta-data support
    • spring boot jar파일은 모두 configuration 설정에 대한 정보를 담은 metadata file을 가지고 있다. : META-INF/spring-configuration-metadata.json 위치에.
    • metadata 는 개발자에게 code completion을 제공한다. 
    • 이런 metadata file은 거의 대부분 컴파일 시점에 자동으로 생성되고, @ConfigurationProperties 어노테이션이 붙은 모든 아이템을 처리하면서 생긴다. (META-INF/additional-spring-configuration-metadata.json 를 통해 물론 추가적인 설정을 넣을 수도 있다. 참고)
    • group을 이용해서 계층적으로 값들을 관리가능하다.  
    • 링크를 통해서 보면 알 수 있듯 여러 어노테이션을 통해서 속성을 지정할 수 있다. 일례로 @deprecation 어노테이션이 붙어있다면 생략되도록 한다. @defaultValue 어노테이션으로, 지정되지 않았을 경우에 넣을 기본 값을 설정할 수도 있다. 

 

정리해보면 둘의 특징은 다음과 같다. 

@Value 어노테이션을 사용하면, 값을 불러올 때 사용하는 단어가 좀 더 제한적이고, metadata에 포함이 안되는 반면 @ConfigurationProperties는 값을 불러올 때 좀 더 느슨하게 가져올 수 있고 metadata에 포함된다. 

이를 type-safe configuration properties 라고 부른다. 

 

스프링 공식문서

 

Spring 공식문서에서는 나만의 컴포넌트를 위해서 configuration 을 세팅하는 거라면 @ConfigurationProperties로 어노테이션 된 POJO에 모아놓을 것을 추천한다. 이렇게 하면 bean에 주입 시에 더 구조화되고, type-safe object를 제공할 것이라 말한다. 

또한, 다양한 properties가 있을 때 계층구조를 다루기 좋다고 말한다. 

 

왜 그런지 생각해보면, 아래 코드에서 볼 수 있듯이 @Value 어노테이션은 각 필드에 어노테이션을 붙여놓기 때문에 노가다성이 있고, 특정 집단에 속한 키들을 묶어놓는다는 느낌이 없다.

@Value("${google.client.id}")
private String clientId;
@Value("${google.client.key}")
private String clientKey;

 

분명 같은 google.client에 속하는 데도, 알아보기가 힘들다.

반면, @ConfigruationProperties 어노테이션을 사용하면, 이렇게 사용할 수 있다.

@ConfigurationProperties("my.service")
public class MyProperties {

    private boolean enabled;

    private InetAddress remoteAddress;

    private final Security security = new Security();

    // getters / setters...

    public static class Security {

        private String username;

        private String password;

        private List<String> roles = new ArrayList<>(Collections.singleton("USER"));

        // getters / setters...

    }

}

  • 이 경우 값을 가져오는 키는 다음과 같다.
  • my.service.enabled , 디폴트 값은 false로
  • my.service.remote-address, String으로 강제된 타입으로.
  • my.service.security.username, Security라는 객체 내의 필드로 생긴다. 이경우 보통 SecurityProperties를 따로 두는게 낫기도 하다.
  • my.service.security.password.
  • my.service.security.roles, String Collection, 디폴트는 ‘USER’로.
  • 이 방식을 사용한 경우 binding 은 Spring MVC처럼 standard Java Beans property descriptor에 의해 일어나므로, 기본 생성자와 getter, setter가 필수적이다. (setter가 필요하지 않은 몇몇 경우는 존재한다. )

 

 

(optional)

 

추가적으로 알아본 생성자 바인딩 방식에 대해서도 정리해보았다. 

이번에 알아본 @ConfiguratonProperties 를 이해하기 위해 필수적인 개념은 아니고 부수적인 개념이다. 

Constructor Binding (링크)

  • 한번 설정되면 다시 변하지 않을 것이라면, ‘immutable fashion’, 즉 볼변하는 방식으로도 만들 수 있다.
  • 생성자 바인딩이라고 부른다.
@ConfigurationProperties("my.service")
public class MyProperties {

    // fields...

    public MyProperties(boolean enabled, InetAddress remoteAddress, Security security) {
        this.enabled = enabled;
        this.remoteAddress = remoteAddress;
        this.security = security;
    }

    // getters...

    public static class Security {

        // fields...

        public Security(String username, String password, @DefaultValue("USER") List<String> roles) {
            this.username = username;
            this.password = password;
            this.roles = roles;
        }

        // getters...

    }

}
  • 위 코드에서 딱 하나의 생성자밖에 없는데 파라미터가 존재하기 때문에, 생성자 바인딩이 사용된다는 것을 내포하게 된다.
  • 그럼 바인더가 해당 생성자를 찾아서 객체를 만드는 데 사용할 것이다.
  • 생성자가 많은 경우에는, @ContructorBinding 주석을 붙여놓아서 생성자 바인딩에 사용할 생성자를 지정할 수 있다.
  • 생성자가 하나밖에 없지만 생성자 바인딩을 원하지 않을 경우엔, @Autowired 어노테이션을 생성자에 붙여놓으면 된다 .
  • 생성자 바인딩은 record와 함께 사용될 수 있다. 레코드가 여러개의 생성자를 갖고 있지 않는한, @ConstructorBinding 어노테이션을 붙일 필요는 없다.
  • 생성자 바인딩을 사용하기 위해서는 보통의 스프링 메커니즘처럼 빈을 사용할수 없다. @EnableConfigurationProperties 또는 configuration property scanning을 써서 설정을 해두어야 한다.
  • native image에서 생성자 바인딩을 사용하기 위해서는, -parameters flag와 함께 컴파일되어야 한다. gradle plugin을 사용하는 경우, 이건 자동으로 일어난다.
  • java.util.Optional과 @ConfigurationProperties를 함께 쓰는 것은 추천되지 않는다. 왜냐면 Optional은 반환 타입에서 사용되기 위한 목적을 갖고 있기 때문이다. Optional 타입을 선언했고 값이 없다고 하더라도, Optional이 들어가지는 않고 null이 들어가게 된다.
  • configuration property scan을 했으면 좋겠는 패키지나, 조건적으로만 사용하고 싶다면, @EnableConfigurationProperties 어노테이션을 사용해도 된다.

 

 

 

 

 

 

 

적용

결국 그래서 내 코드가 어떻게 바뀌었는지 설명하며 글을 마치겠다. 

나의 경우 생성자 바인딩까지는 필요없었고, @Value를 사용했던 기존 코드에서 @ConfiguratonProperties 로 바꾸는 작업을 진행했다.결론적으로 레포지토리에 적용된 코드는 다음과 같다.

  • 이전 코드

  • 이후 코드

application.properties 안의 내용 변경: 

- kakao.code.url= … 대신 kakao.code-url= … 으로 변경 ('.'대신 '-' 사용!)

 

 

@ConfigurationProperties 를 이용해서 좀 더 편리하게 설정 값들을 사용할 수 있을 것 같다! 

spring 공식문서를 읽는 것도 큰 도움이 되었다. 

반응형

'개발 > java' 카테고리의 다른 글

[Spring Security] Spring Security 의 동작과정과 구현  (0) 2024.04.14
[Spring - JPA] 4. 엔티티 매핑  (0) 2024.03.16
[Spring - JPA] 3. 영속성관리  (1) 2024.02.18
[Spring - JPA] 2. JPA 개요  (1) 2024.02.18
[Spring - JPA] 1. JPA 소개  (1) 2024.02.03