Please enable JavaScript to view the comments powered by Disqus.스프링의 @ConfigurationProperites 의 정확한 사용법, properties 읽어오기
Search

스프링의 @ConfigurationProperites 의 정확한 사용법, properties 읽어오기

태그
Spring
spring boot
Configuration
properties
yaml
공개여부
작성일자
2022/09/07
Spring 의 몇몇 값들을 properties 를 이용해서 가져오는 방법은 상당히 유용하다. 추가 배포 없이 properties 만 동적으로 reload 시킬 수도 있고, 중요한 key 들을 github secret 을 이용해 관리할 수도 있기 때문이다.
Spring 에 상당히 멋진 기능이 포함되었는데 의외로 널리 알려지지 않은것 같은데다 한번 제대로 정리할 필요가 있다고 생각하여 이번에 사용법을 정리 하고자한다.
개요(필요한 것만 보셔도 됩니다)

소개

Spring boot 는 configuration 과 몇몇 속성을 *.properties 혹은 *.yaml 에서 쉽게 가져오는 방법을 제공하기 시작했다.
application.properties 나 application.yml 의 속성을 가져올 때 기존엔 다음과 같이 사용했을 것이다
@Value( "${jdbc.url}" ) private String jdbcUrl;
Java
하지만, 이젠 이를 더 객체지향적으로 다룰 수 있으며, 덕분에 테스트에 사용하기도 편리해졌다.

Setup

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.6.4</version> <relativePath/> </parent>
XML
먼저 이와 같은 설정을 gradle 에 추가해야 한다. 이 기능은 2.2.0 부터 제공되었다.

Property를 정의해보자

Official document 에선 configuration properties 를 POJO를 통해 분리하는 것을 권장한다.
다음의 propertes 가 있다고 가정하자
mail: hostname: host@mail.com port: 9000 mail: mailer@mail.com
YAML
예시를 위해 사용할 application.yml
이 속성을 다음과 같이 가져와서 사용할 수 있다.
@Configuration @ConfigurationProperties(prefix = "mail") public class ConfigProperties { private String hostName; private int port; private String from; // getter 와 setter 들 정의 }
Java
여기에서 @Configuration 을 사용하는 이유는 Spring 에서 이 pojo 를 bean으로 만들기 위해서 이다.
@ConfigurationProperties 는 동일한 prefix 를 갖는 hierarchical properties 를 다루기에 최고이다.
yaml 에서 사용하면 아주 달달하다.

Properties 는 camel? snake? 무엇을 사용해야 할까?

mail.hostName mail.hostname mail.host_name mail.host-name mail.HOST_NAME
YAML
위의 어느 경우라도 private String hostName 과 binding 된다.
Debugging 하면서 발견한것은 무엇을 사용하든 Spring 내부에서 host-name 을 사용하는 듯 하다.

Spring Boot 2.2

스프링 2.2 버젼부터 @ConfigurationProperties 가 달려있는 class 는 @EnableConfigurationProperties 또는 @Component 대신 @ConfigurationPropertiesScan 을 통해 class path 를 찾을 수 있다.
그러므로 @Component@Configruation 과 같은 annotation 을 붙이지 않아도 되고 @EnabledConfigurationProperties 또 사용할 필요가 없다.
주의! 원문에선 @Component @EnabledConfigruationProperties 등을 사용하지 말라고 나오지만, 이들을 사용하지 않고 @ConfigurationPropertiesScan 만 사용하면 compile error 가 발생된다. 원인은 후에 찾아서 달아두도록 하겠다.
@ConfigurationProperties(prefix = "mail") @ConfigurationPropertiesScan public class ConfigProperties { private String hostName; private int port; private String from; }
Java
@Configuration 대신 @ConfigurationPropertiesScan 를 붙였다.
Class path scanner 는 @SpringBootApplication 으로 활성화 되어 @Component 가 없더라도 ConfigProperties 를 찾는다.
추가로 @ConfigurationPropertiesScan 의 path 를 변경하여 다른 location을 참조할 수 있다.
@SpringBootApplication @ConfigurationPropertiesScan("com.baeldung.configurationproperties") public class EnableConfigurationDemoApplication { public static void main(String[] args) { SpringApplication.run(EnableConfigurationDemoApplication.class, args); } }
Java
@ConfigurationPropertiesScan("me.yevgnenll.configurationproperties") 로 다른 위치를 나타낸다.
위와 같이 명시하면 me.yevgnenll.proeprties 패키지를 참조한다.

중첩 properties

그런데 properties class 들도 객체지향적으로 사용하고 싶을 수 있으며, List, Map, 다른 class 등 더 명시적인 자료구조를 사용할 수도 있다.
다음의 새로운 class 가 있다고 가정하자
public class Credentials { private String authMethod; private String username; private String password; // getter, setter }
Java
ConfigProperties 에 다음과 같이 정의한다.
public class ConfigProperties { private String host; private int port; private String from; private List<String> defaultRecipients; private Map<String, String> additionalHeaders; private Credentials credentials; // getter, setter }
Java
이 코드가 참조하는 properties 는 다음과 같다.
#Simple properties mail: hostname: mailer@mail.com port: 9000 from: mailer@mail.com #List properties defaultRecipients[0]: admin@mail.com defaultRecipients[1]: owner@mail.com #Map Properties additionalHeaders: redelivery: true secure: true #Object properties credentials: username: john password: password authMethod: SHA1
YAML
위 클래스와 binding 할 properties

@ConfigurationProperties@Bean 사용하기

@ConfigurationProperties@Bean 이 붙은 method 를 함께 사용할 수 있다.
이는 third-party 와 같이 우리의 통제를 벗어난 properties 를 binding 할 때 유용하다.
public class Item { private String name; private int size; // getters, setters }
Java
@Configuration public class ConfigProperties { @Bean @ConfigurationProperties(prefix = "item") public Item item() { return new Item(); } }
Java
item- prefix 인 property 는 item() 에 binding 되어 item instance 를 생성한다.

Property validation

@ConfigurationProperties 는 JSR-30 format 에 해당하는 validation 기능을 제공한다.
@NotBlank private String hostName; @Length(max = 4, min = 1) private String authMethod; @Min(1025) @Max(65536) private int port; @Pattern(regexp = "^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,6}$") private String from;
Java
이와 같이 validation 을 정의해두면 별도의 valid 함수를 정의할 필요가 없다.
또한 validation 을 만족하지 못한다면 IllegalStateException 을 throw 한다.

Property 변경하기

@ConfigurationProperties 는 여러가지 다양한 type 으로 변환되는 기능을 제공한다.

Duration

다음과 같이 두개의 Duration 필드가 있다.
@ConfigurationProperties(prefix = "conversion") public class PropertyConversion { private Duration timeInDefaultUnit; private Duration timeInNano; ... }
Java
단위
properties 표현
nanoseconds
ns
microseconds
us
milliseconds
ms
secodns
s
minutes
m
hours
h
days
d
conversion: timeInDefaultUnit: 10 timeInNano: 9ns
YAML
timeInDefaultUnit 는 10miliseconds 로 정의되고, timeInNano 는 nano seconds 로 정의된다.
각 숫자와 단위는 표와 같다.
여기서 아무 단위를 입력하지 않는다면 default 로 milliseconds 가 적용된다.
또한, 다음과 같이 override 하여 Duration 을 정의할 수 있다.
@DurationUnit(ChronoUnit.DAYS) private Duration timeInDays;
Java
conversion: timeInDays: 2
YAML

DataSize

이건 몰랐는데 data size 역시 정의가 가능하다.
private DataSize sizeInDefaultUnit; private DataSize sizeInGB; @DataSizeUnit(DataUnit.TERABYTES) private DataSize sizeInTB;
Java
단위
표현
Bytes
B
KilloBytes
KB
MegaBytes
MB
GigaBytes
GB
TeraBytes
TB
conversion: sizeInDefaultUnit: 300 sizeInGB: 2GB sizeInTB: 4
YAML
여기도 default 단위가 존재하는데 bytes 가 된다.
역시 @DataSizeUnit 을 사용해 적절한 단위로 override 가 가능하다.

Custom Converter

이 항목이 내가 이 글을 작성한 원인이다.
public class Employee { private String name; private double salary; }
Java
conversion.employee: john,2000
YAML
이와 같이 proeprties 를 정의하면 다음으로 conversion 된다.
private Employee employee;
Java
그런데 여기서 Converter interface 를 정의하여 @ConfigurationPropertiesBinding 을 등록해줘야 의도한대로 값이 binding 된다.
@Component @ConfigurationPropertiesBinding public class EmployeeConverter implements Converter<String, Employee> { @Override public Employee convert(String from) { String[] data = from.split(","); return new Employee(data[0], Double.parseDouble(data[1])); } }
Java

Immutable @ConfigruationProperties binding 하기

이 기능은 java 보다도 kotlin 에서 더 많이 사용했던 것 같다.
외부 API를 연동하기 위해 @ConfigruationProperties 를 사용할 경우, 혹은 어떠한 key, secret 을 사용할 땐 딱히 값이 변경되지 않는 경우가 상당하다.
@ConfigurationProperties(prefix = "mail.credentials") @ConstructorBinding public class ImmutableCredentials { private final String authMethod; private final String username; private final String password; public ImmutableCredentials(String authMethod, String username, String password) { this.authMethod = authMethod; this.username = username; this.password = password; } public String getAuthMethod() { return authMethod; } public String getUsername() { return username; } public String getPassword() { return password; } }
Java
여기서 주의해야 할 점은 위에서 볼 수 있듯이 constructor 를 정의하여 모든 parameter 를 명시해야 한다.
또한, 모든 field 는 final 을 명시하여 불변으로 정의했다.
따라서 setter 를 구현 할 필요가 없다.
그런데 이 immutable properties 를 사용하기 위해선 강조해야 하는 부분이 있는데
@EnableConfigurationProperties 혹은 @ConfigurationPropertiesScan 을 반드시 함께 사용해야 한다.

Java 14의 records

Java 14 에선 records 라 하는 class 가 추가되었다.
이에 대해선 따로 자세히 다룰 예정이며, 여기선 개념이나 용도 목적등을 따로 언급하지 않는다.
이 Records 클래스는 immutable 을 다루는데 아주 좋으며, DTO 로 사용하기에 알맞다고 한다.
이 부분 때문에 자바 record 는 무엇인가? 라는 글을 작성했다
@ConstructorBinding @ConfigurationProperties(prefix = "mail.credentials") public record ImmutableCredentials( String authMethod, String username, String password) { }
Java
따로 필드가 없음
여기선 getter 도 setter 도 필요하지 않다.
Spring boot 2.6 부터 존재하며, single-constructor 이면 @ContructorBinding 을 누락할 수 있다.
하지만, multiple constructor 이면 @ContructorBinding 가 반드시 필요하다.
하지만, @ContructorBinding 를 사용한다는 의미는 property binding 임을 나타내는데 사용하므로 여전히 붙여둔다고 한다.

결론

이 글에서 @ConfigurationProperties 와 Nested, bean validation 을 위한 기능을 살펴보았다.
이 글의 원본은 https://www.baeldung.com/configuration-properties-in-spring-boot 이며 이를 번역하면서 약간 내 입맛대로 수정했다.
더 많은 개발자들이 @Value 의 감옥에서 벗어나 더 자유롭게 properties 를 구성하며 이를 통해 OOP를 강화하고, test code 가 더 유연해졌으면 좋겠다.
Properties class 를 정의하면서 가장 편한 것은 test code 를 작성하면 다루기가 너무 쉬워진다는 것이다.

참고한 링크들