본문으로 건너뛰기

1. Type-Safe Configuration Properties 소개

@Value("${property}") 어노테이션을 사용한 설정 프로퍼티 주입은 여러 프로퍼티를 다루거나 계층적 데이터 구조를 가진 경우 번거로울 수 있습니다. Spring Boot는 강타입 빈을 통해 애플리케이션 설정을 관리하고 검증할 수 있는 대안적인 방법을 제공합니다.

Type-Safe Configuration의 장점
  • 타입 안전성: 컴파일 타임에 타입 오류를 검출할 수 있습니다.
  • 구조화된 설정: 관련된 설정들을 하나의 클래스로 그룹화할 수 있습니다.
  • IDE 지원: 자동 완성과 설정 메타데이터를 제공합니다.
  • 검증 기능: JSR-303 검증 어노테이션을 활용할 수 있습니다.

2. JavaBean Properties Binding

표준 JavaBean 프로퍼티를 선언하는 빈을 바인딩할 수 있습니다.

2.1 기본 예제

import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

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

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

private boolean enabled;

private InetAddress remoteAddress;

private final Security security = new Security();

// getter / setter 메서드들...

public boolean isEnabled() {
return this.enabled;
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public InetAddress getRemoteAddress() {
return this.remoteAddress;
}

public void setRemoteAddress(InetAddress remoteAddress) {
this.remoteAddress = remoteAddress;
}

public Security getSecurity() {
return this.security;
}

public static class Security {

private String username;

private String password;

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

// getter / setter 메서드들...

public String getUsername() {
return this.username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return this.password;
}

public void setPassword(String password) {
this.password = password;
}

public List<String> getRoles() {
return this.roles;
}

public void setRoles(List<String> roles) {
this.roles = roles;
}
}
}

2.2 매핑되는 프로퍼티

위의 POJO는 다음 프로퍼티들을 정의합니다:

  • my.service.enabled - 기본값 false
  • my.service.remote-address - String에서 변환 가능한 타입
  • my.service.security.username - 중첩된 "security" 객체
  • my.service.security.password
  • my.service.security.roles - 기본값 USER를 가진 String 컬렉션
바인딩 요구사항

이러한 방식은 기본 빈 생성자와 getter/setter 메서드에 의존합니다. 바인딩은 Spring MVC와 동일하게 표준 Java Beans 프로퍼티 디스크립터를 통해 수행됩니다.

2.3 Setter 생략 가능한 경우

다음 경우에는 setter를 생략할 수 있습니다:

  • Map: 초기화되어 있다면 getter만 필요하고 setter는 필수가 아닙니다
  • 컬렉션과 배열: 인덱스나 쉼표로 구분된 값으로 접근 가능합니다
  • 중첩 POJO: 초기화되어 있다면 setter가 필요하지 않습니다
Lombok 사용 시 주의사항

Project Lombok을 사용하는 경우, 특별한 생성자를 생성하지 않도록 주의하세요. 컨테이너가 객체를 자동으로 인스턴스화할 때 사용됩니다.

3. Constructor Binding

이전 예제를 불변(immutable) 방식으로 다시 작성할 수 있습니다.

3.1 불변 클래스 예제

import java.net.InetAddress;
import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;

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

private final boolean enabled;

private final InetAddress remoteAddress;

private final Security security;

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

// getter 메서드들...

public boolean isEnabled() {
return this.enabled;
}

public InetAddress getRemoteAddress() {
return this.remoteAddress;
}

public Security getSecurity() {
return this.security;
}

public static class Security {

private final String username;

private final String password;

private final List<String> roles;

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

// getter 메서드들...

public String getUsername() {
return this.username;
}

public String getPassword() {
return this.password;
}

public List<String> getRoles() {
return this.roles;
}
}
}

3.2 Constructor Binding 규칙

  • 단일 매개변수화된 생성자가 있으면 constructor binding이 사용됩니다
  • 여러 생성자가 있는 경우 @ConstructorBinding 어노테이션으로 지정할 수 있습니다
  • Constructor binding을 사용하지 않으려면 매개변수화된 생성자에 @Autowired를 붙이거나 private으로 만듭니다
기본값 지정

@DefaultValue 어노테이션을 사용하여 생성자 매개변수와 레코드 컴포넌트에 기본값을 지정할 수 있습니다. 변환 서비스가 문자열 값을 대상 타입으로 변환합니다.

3.3 Constructor Binding 활성화

Constructor binding을 사용하려면 @EnableConfigurationProperties 또는 configuration property scanning으로 클래스를 활성화해야 합니다.

주의사항
  • 일반적인 Spring 메커니즘으로 생성된 빈(@Component, @Bean 메서드 등)에서는 constructor binding을 사용할 수 없습니다
  • -parameters 옵션으로 컴파일해야 합니다 (Spring Boot의 Gradle 플러그인이나 Maven에서 자동으로 처리)

4. @ConfigurationProperties 타입 활성화

Spring Boot는 @ConfigurationProperties 타입을 바인딩하고 빈으로 등록하는 인프라를 제공합니다.

4.1 클래스별 활성화

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(SomeProperties.class)
public class MyConfiguration {

}
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("some.properties")
public class SomeProperties {

}

4.2 Configuration Property Scanning

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;

@SpringBootApplication
@ConfigurationPropertiesScan({ "com.example.app", "com.example.another" })
public class MyApplication {

}
빈 이름 규칙

Configuration property scanning이나 @EnableConfigurationProperties를 통해 등록된 빈의 이름은 <prefix>-<fqn> 형식을 따릅니다. 여기서 <prefix>는 어노테이션에 지정된 환경 키 접두사이고, <fqn>은 빈의 정규화된 이름입니다.

4.3 권장사항

최선의 관례

@ConfigurationProperties는 환경과만 관련되어야 하며, 특히 컨텍스트에서 다른 빈을 주입하지 않는 것이 좋습니다. 다른 빈을 주입해야 하는 경우 @Component를 사용하고 JavaBean 기반 프로퍼티 바인딩을 사용하세요.

5. @ConfigurationProperties 타입 사용

이 설정 스타일은 SpringApplication 외부 YAML 설정과 특히 잘 작동합니다.

5.1 YAML 설정 예제

my:
service:
remote-address: 192.168.1.1
security:
username: "admin"
roles:
- "USER"
- "ADMIN"

5.2 서비스에서 사용

import org.springframework.stereotype.Service;

@Service
public class MyService {

private final MyProperties properties;

public MyService(MyProperties properties) {
this.properties = properties;
}

public void openConnection() {
Server server = new Server(this.properties.getRemoteAddress());
server.start();
// ...
}

// ...
}
메타데이터 생성

@ConfigurationProperties를 사용하면 IDE에서 자동 완성을 제공하는 메타데이터 파일을 생성할 수 있습니다.

6. 서드파티 설정

클래스에 @ConfigurationProperties를 사용하는 것 외에도 public @Bean 메서드에서도 사용할 수 있습니다.

6.1 서드파티 컴포넌트 설정

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(proxyBeanMethods = false)
public class ThirdPartyConfiguration {

@Bean
@ConfigurationProperties("another")
public AnotherComponent anotherComponent() {
return new AnotherComponent();
}
}

another 접두사로 정의된 JavaBean 프로퍼티는 AnotherComponent 빈에 매핑됩니다.

7. 유연한 바인딩 (Relaxed Binding)

Spring Boot는 Environment 프로퍼티를 @ConfigurationProperties 빈에 바인딩할 때 유연한 규칙을 사용합니다.

7.1 바인딩 예제

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

@ConfigurationProperties("my.main-project.person")
public class MyPersonProperties {

private String firstName;

public String getFirstName() {
return this.firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}
}

7.2 사용 가능한 프로퍼티 이름들

프로퍼티설명
my.main-project.person.first-name케밥 케이스 (권장)
my.main-project.person.firstName표준 카멜 케이스
my.main-project.person.first_name언더스코어 표기법
MY_MAINPROJECT_PERSON_FIRSTNAME대문자 형식 (환경 변수)
접두사 규칙

어노테이션의 접두사 값은 반드시 케밥 케이스여야 합니다 (소문자이고 -로 구분).

7.3 프로퍼티 소스별 유연한 바인딩 규칙

프로퍼티 소스단순 타입리스트
Properties 파일카멜 케이스, 케밥 케이스, 언더스코어[ ] 또는 쉼표로 구분된 값
YAML 파일카멜 케이스, 케밥 케이스, 언더스코어표준 YAML 리스트 또는 쉼표로 구분된 값
환경 변수언더스코어를 구분자로 하는 대문자언더스코어로 둘러싸인 숫자 값
시스템 프로퍼티카멜 케이스, 케밥 케이스, 언더스코어[ ] 또는 쉼표로 구분된 값
권장사항

가능하면 프로퍼티를 소문자 케밥 형식으로 저장하는 것을 권장합니다 (예: my.person.first-name=Rod).

8. Map 바인딩

Map 프로퍼티에 바인딩할 때는 원래 키 값이 보존되도록 특별한 대괄호 표기법을 사용해야 할 수 있습니다.

8.1 Map 바인딩 예제

my.map[/key1]=value1
my.map[/key2]=value2
my.map./key3=value3

위 프로퍼티들은 /key1, /key2, key3를 키로 가진 Map에 바인딩됩니다. key3에서는 대괄호로 둘러싸이지 않았기 때문에 슬래시가 제거되었습니다.

YAML 파일 주의사항

YAML 파일에서는 키가 올바르게 파싱되도록 대괄호를 따옴표로 둘러싸야 합니다.

my:
map:
"[/key1]": value1
"[/key2]": value2
"/key3": value3

8.2 스칼라 값과 Map

스칼라 값에 바인딩할 때는 .이 포함된 키를 대괄호로 둘러쌀 필요가 없습니다. 스칼라 값에는 열거형과 Object를 제외한 java.lang 패키지의 모든 타입이 포함됩니다.

9. 환경 변수에서 바인딩

대부분의 운영 체제는 환경 변수 이름에 대해 엄격한 규칙을 적용합니다. Spring Boot의 유연한 바인딩 규칙은 가능한 한 이러한 명명 제한과 호환되도록 설계되었습니다.

9.1 변환 규칙

정규 형식의 프로퍼티 이름을 환경 변수 이름으로 변환하려면:

  1. 점(.)을 언더스코어(_)로 바꿉니다
  2. 대시(-)를 제거합니다
  3. 대문자로 변환합니다

예: spring.main.log-startup-infoSPRING_MAIN_LOGSTARTUPINFO

9.2 객체 리스트 바인딩

환경 변수를 사용하여 객체 리스트에 바인딩할 수도 있습니다. 리스트에 바인딩하려면 변수 이름에서 요소 번호를 언더스코어로 둘러싸야 합니다.

예: my.service[0].otherMY_SERVICE_0_OTHER

9.3 환경 변수에서 Map 바인딩

import java.util.HashMap;
import java.util.Map;

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

@ConfigurationProperties("my.props")
public class MyMapsProperties {

private final Map<String, String> values = new HashMap<>();

public Map<String, String> getValues() {
return this.values;
}
}

MY_PROPS_VALUES_KEY=value로 설정하면 values Map에 {"key"="value"} 항목이 포함됩니다.

대소문자 변환

환경 변수 이름만 소문자로 변환되고 값은 변환되지 않습니다. MY_PROPS_VALUES_KEY=VALUE로 설정하면 {"key"="VALUE"} 항목이 포함됩니다.

10. 복합 타입 병합

리스트가 여러 곳에서 구성되면 전체 리스트를 교체하는 방식으로 재정의가 작동합니다.

10.1 리스트 병합 예제

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

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

@ConfigurationProperties("my")
public class MyProperties {

private final List<MyPojo> list = new ArrayList<>();

public List<MyPojo> getList() {
return this.list;
}
}

다음 설정을 고려하세요:

my.list[0].name=my name
my.list[0].description=my description
#---
spring.config.activate.on-profile=dev
my.list[0].name=my another name

dev 프로필이 활성화되면 리스트는 여전히 하나의 항목만 포함합니다 (이름은 "my another name", 설명은 null).

10.2 Map 병합

Map 프로퍼티의 경우 여러 소스에서 가져온 프로퍼티 값으로 바인딩할 수 있습니다. 하지만 여러 소스의 동일한 프로퍼티에 대해서는 가장 높은 우선순위를 가진 것이 사용됩니다.

import java.util.LinkedHashMap;
import java.util.Map;

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

@ConfigurationProperties("my")
public class MyProperties {

private final Map<String, MyPojo> map = new LinkedHashMap<>();

public Map<String, MyPojo> getMap() {
return this.map;
}
}
병합 규칙 적용 범위

이러한 병합 규칙은 파일뿐만 아니라 모든 프로퍼티 소스의 프로퍼티에 적용됩니다.

11. 속성 변환

Spring Boot는 @ConfigurationProperties 빈에 바인딩할 때 외부 애플리케이션 프로퍼티를 올바른 타입으로 강제 변환하려고 시도합니다.

11.1 커스텀 타입 변환

커스텀 타입 변환이 필요한 경우 다음을 제공할 수 있습니다:

  • ConversionService 빈 (빈 이름 conversionService)
  • 커스텀 프로퍼티 에디터 (CustomEditorConfigurer 빈 통해)
  • 커스텀 컨버터 (@ConfigurationPropertiesBinding으로 어노테이션된 빈 정의)
초기화 순서 주의

프로퍼티 변환에 사용되는 빈은 애플리케이션 라이프사이클 초기에 요청되므로 ConversionService가 사용하는 종속성을 제한해야 합니다.

11.2 Duration 변환

Spring Boot는 duration 표현에 대한 전용 지원을 제공합니다.

import java.time.Duration;
import java.time.temporal.ChronoUnit;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DurationUnit;

@ConfigurationProperties("my")
public class MyProperties {

@DurationUnit(ChronoUnit.SECONDS)
private Duration sessionTimeout = Duration.ofSeconds(30);

private Duration readTimeout = Duration.ofMillis(1000);

// getter / setter 메서드들...
}

11.3 지원되는 Duration 형식

  • 일반적인 long 표현 (기본 단위는 밀리초)
  • 표준 ISO-8601 형식
  • 값과 단위가 결합된 읽기 쉬운 형식 (10s는 10초)

11.4 지원되는 단위

  • ns - 나노초
  • us - 마이크로초
  • ms - 밀리초
  • s - 초
  • m - 분
  • h - 시간
  • d - 일

11.5 DataSize 변환

Spring Framework의 DataSize 값 타입을 사용하여 바이트 크기를 표현할 수 있습니다.

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.convert.DataSizeUnit;
import org.springframework.util.unit.DataSize;
import org.springframework.util.unit.DataUnit;

@ConfigurationProperties("my")
public class MyProperties {

@DataSizeUnit(DataUnit.MEGABYTES)
private DataSize bufferSize = DataSize.ofMegabytes(2);

private DataSize sizeThreshold = DataSize.ofBytes(512);

// getter/setter 메서드들...
}

11.6 지원되는 DataSize 단위

  • B - 바이트
  • KB - 킬로바이트
  • MB - 메가바이트
  • GB - 기가바이트
  • TB - 테라바이트

11.7 Base64 데이터 변환

Spring Boot는 Base64로 인코딩된 바이너리 데이터 해결을 지원합니다.

my.property=base64:SGVsbG8gV29ybGQ=

Resource 프로퍼티는 리소스 경로를 제공하는 데도 사용할 수 있어 더욱 다양하게 활용할 수 있습니다.

12. @ConfigurationProperties 검증

Spring Boot는 Spring의 @Validated 어노테이션으로 어노테이션된 @ConfigurationProperties 클래스의 검증을 시도합니다.

12.1 기본 검증

import java.net.InetAddress;

import jakarta.validation.constraints.NotNull;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

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

@NotNull
private InetAddress remoteAddress;

// getter/setter 메서드들...

public InetAddress getRemoteAddress() {
return this.remoteAddress;
}

public void setRemoteAddress(InetAddress remoteAddress) {
this.remoteAddress = remoteAddress;
}
}

12.2 중첩 검증

중첩 프로퍼티에 검증을 적용하려면 관련 필드에 @Valid를 어노테이션해야 합니다.

import java.net.InetAddress;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;

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

@NotNull
private InetAddress remoteAddress;

@Valid
private final Security security = new Security();

// getter/setter 메서드들...

public static class Security {

@NotEmpty
private String username;

// getter/setter 메서드들...

public String getUsername() {
return this.username;
}

public void setUsername(String username) {
this.username = username;
}
}
}

12.3 커스텀 Validator

configurationPropertiesValidator라는 빈 정의를 생성하여 커스텀 Spring Validator를 추가할 수도 있습니다.

static 메서드 권장

@Bean 메서드는 static으로 선언해야 합니다. configuration properties validator는 애플리케이션 라이프사이클 초기에 생성되므로, @Bean 메서드를 static으로 선언하면 @Configuration 클래스를 인스턴스화하지 않고도 빈을 생성할 수 있습니다.

Actuator 지원

spring-boot-actuator 모듈에는 모든 @ConfigurationProperties 빈을 노출하는 엔드포인트가 포함되어 있습니다. 웹 브라우저에서 /actuator/configprops를 가리키거나 동등한 JMX 엔드포인트를 사용하세요.

13. @ConfigurationProperties vs @Value

@Value 어노테이션은 핵심 컨테이너 기능이지만 type-safe configuration properties와 동일한 기능을 제공하지 않습니다.

13.1 기능 비교

기능@ConfigurationProperties@Value
유연한 바인딩제한적
메타데이터 지원아니오
SpEL 평가아니오

13.2 @Value 사용 시 권장사항

@Value를 사용하고 싶다면 정규 형식(소문자만 사용하는 케밥 케이스)을 사용하여 프로퍼티 이름을 참조하는 것을 권장합니다.

// 권장
@Value("${demo.item-price}")

// 비권장
@Value("${demo.itemPrice}")
최종 권장사항

자체 컴포넌트에 대한 설정 키 세트를 정의하는 경우, @ConfigurationProperties로 어노테이션된 POJO로 그룹화하는 것을 권장합니다. 이렇게 하면 자체 빈에 주입할 수 있는 구조화되고 type-safe한 객체를 제공할 수 있습니다.

13.3 SpEL 표현식 주의사항

SpEL 처리 시점

애플리케이션 프로퍼티 파일의 SpEL 표현식은 파일을 파싱하고 환경을 채울 때 처리되지 않습니다. 하지만 @Value에서 SpEL 표현식을 작성하는 것은 가능합니다. 애플리케이션 프로퍼티 파일의 프로퍼티 값이 SpEL 표현식인 경우 @Value를 통해 사용될 때 평가됩니다.


이 문서는 Spring Boot의 Type-Safe Configuration Properties에 대한 포괄적인 가이드입니다. 실제 프로젝트에서 구조화되고 안전한 설정 관리를 위해 @ConfigurationProperties를 적극 활용해보세요.