Spring ConfigurationProperties 바인딩

@ConfigurationProperties란?

Spring Boot의 @ConfigurationPropertiesapplication.yml 또는 환경 변수의 설정값을 타입 안전한 Java 객체로 바인딩하는 메커니즘이다. @Value와 달리 계층 구조, 컬렉션, 중첩 객체를 자연스럽게 매핑하며 검증(Validation)까지 통합할 수 있다.

기본 사용법: Record 바인딩

Spring Boot 3.x부터 Java Record를 @ConfigurationProperties에 직접 사용할 수 있다. 불변(immutable) 설정 객체를 생성자 바인딩으로 만들 수 있어 가장 권장되는 패턴이다.

// application.yml
// app:
//   name: my-service
//   api:
//     base-url: https://api.example.com
//     timeout: 5s
//     retry-count: 3

@ConfigurationProperties(prefix = "app")
public record AppProperties(
    String name,
    ApiProperties api
) {
    public record ApiProperties(
        String baseUrl,
        Duration timeout,
        @DefaultValue("3") int retryCount
    ) {}
}

@DefaultValue로 기본값을 지정하고, Duration 타입은 5s, 2m, 1h 같은 문자열을 자동 파싱한다.

활성화 방법: @EnableConfigurationProperties vs @ConfigurationPropertiesScan

// 방법 1: 명시적 등록
@Configuration
@EnableConfigurationProperties(AppProperties.class)
public class AppConfig {}

// 방법 2: 패키지 스캔 (Spring Boot 2.2+)
@SpringBootApplication
@ConfigurationPropertiesScan("com.example.config")
public class Application {}

프로젝트 규모가 커지면 @ConfigurationPropertiesScan이 편리하다. 단, 스캔 범위를 명시해 불필요한 클래스가 등록되지 않도록 한다.

Relaxed Binding: 유연한 속성 매핑

Spring Boot는 Relaxed Binding이라는 규칙으로 다양한 형식의 속성 이름을 동일 필드에 매핑한다.

소스 형식 예시 매핑 대상
kebab-case (yml 권장) api.base-url baseUrl
camelCase api.baseUrl baseUrl
UPPER_SNAKE (환경 변수) APP_API_BASE_URL baseUrl
underscore api.base_url baseUrl

환경 변수로 설정을 오버라이드할 때 이 규칙을 알아야 한다. app.api.base-url은 환경 변수 APP_API_BASE_URL로 덮어쓸 수 있다. Spring Profiles 환경 분리와 함께 사용하면 환경별 설정 관리가 깔끔해진다.

컬렉션과 Map 바인딩

// application.yml
// storage:
//   endpoints:
//     - https://s3-1.example.com
//     - https://s3-2.example.com
//   headers:
//     X-Api-Key: secret123
//     X-Region: ap-northeast-2

@ConfigurationProperties(prefix = "storage")
public record StorageProperties(
    List<String> endpoints,
    Map<String, String> headers
) {}

List는 인덱스 기반(endpoints[0])으로, Map은 키-값 쌍으로 바인딩된다. 환경 변수에서 List를 설정할 때는 STORAGE_ENDPOINTS_0=https://... 형식을 사용한다.

Validation: @Validated + JSR-303

설정값 검증은 애플리케이션 시작 시점에 수행되므로, 잘못된 설정이 런타임까지 도달하지 않는다.

@Validated
@ConfigurationProperties(prefix = "app.api")
public record ApiProperties(
    @NotBlank String baseUrl,
    @DurationMin(seconds = 1) @DurationMax(minutes = 1) Duration timeout,
    @Min(0) @Max(10) int retryCount
) {}

// 잘못된 설정 시 시작 실패:
// Binding validation errors on app.api
//  - Field 'baseUrl': must not be blank
//  - Field 'timeout': must be at least 1s

@Validated를 클래스 레벨에 선언해야 JSR-303 어노테이션이 동작한다. 중첩 객체에는 @Valid를 추가한다.

@Value와의 차이점

비교 항목 @ConfigurationProperties @Value
타입 안전성 ✅ 컴파일 타임 ❌ 런타임 SpEL
중첩 구조 ✅ 자연스러운 계층 ❌ 플랫 키만
Relaxed Binding ✅ 지원 ❌ 미지원
검증 ✅ JSR-303 ❌ 수동 처리
테스트 ✅ Mock 객체 주입 용이 ⚠️ 필드 주입 Mocking 어려움
적합 상황 설정 그룹 (3개+) 단일 값 1~2개

Custom Starter에서의 활용

사내 공통 라이브러리를 Spring Boot Custom Starter로 만들 때 @ConfigurationProperties는 핵심이다.

// my-company-starter의 자동 설정
@AutoConfiguration
@EnableConfigurationProperties(CompanyApiProperties.class)
@ConditionalOnProperty(prefix = "company.api", name = "enabled", havingValue = "true")
public class CompanyApiAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    public CompanyApiClient companyApiClient(CompanyApiProperties props) {
        return new CompanyApiClient(props.baseUrl(), props.timeout());
    }
}

// META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
// com.company.starter.CompanyApiAutoConfiguration

사용하는 쪽에서는 application.ymlcompany.api.base-url만 설정하면 된다. IDE 자동 완성을 위해 spring-boot-configuration-processor를 추가하면 META-INF/spring-configuration-metadata.json이 생성된다.

IDE 자동 완성: Configuration Processor

// build.gradle.kts
dependencies {
    annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
}

// 빌드 후 생성되는 메타데이터 예시
// target/classes/META-INF/spring-configuration-metadata.json
{
  "groups": [{ "name": "app.api", "type": "com.example.ApiProperties" }],
  "properties": [
    { "name": "app.api.base-url", "type": "java.lang.String" },
    { "name": "app.api.timeout", "type": "java.time.Duration", "defaultValue": "5s" }
  ]
}

이 메타데이터 파일 덕분에 IntelliJ/VSCode에서 application.yml 작성 시 속성 자동 완성, 타입 힌트, 설명이 표시된다.

프로파일별 오버라이드 패턴

// application.yml (기본값)
app:
  api:
    base-url: https://api.example.com
    timeout: 5s

// application-prod.yml (프로덕션 오버라이드)
app:
  api:
    timeout: 2s    # 프로덕션은 타임아웃을 짧게

// 환경 변수 최종 오버라이드 (K8s ConfigMap → env)
// APP_API_BASE_URL=https://api-internal.prod.svc.cluster.local

우선순위: 환경 변수 > application-{profile}.yml > application.yml. K8s 배포 시 ConfigMap/Secret을 환경 변수로 주입하면 코드 변경 없이 설정을 바꿀 수 있다.

테스트에서의 활용

@SpringBootTest
@TestPropertySource(properties = {
    "app.api.base-url=https://mock.test",
    "app.api.timeout=1s",
    "app.api.retry-count=0"
})
class ApiServiceTest {

    @Autowired
    private AppProperties properties;

    @Test
    void shouldBindTestProperties() {
        assertThat(properties.api().baseUrl()).isEqualTo("https://mock.test");
        assertThat(properties.api().timeout()).isEqualTo(Duration.ofSeconds(1));
    }
}

// Record이므로 단위 테스트에서 직접 생성도 가능
@Test
void unitTest() {
    var props = new AppProperties.ApiProperties("https://mock", Duration.ofSeconds(1), 0);
    var service = new ApiService(props);
    // ...
}

주의사항과 베스트 프랙티스

  • setter 바인딩 vs 생성자 바인딩 — Record나 @ConstructorBinding을 사용한 불변 객체를 권장한다. JavaBean 방식(setter)은 설정값이 런타임에 변경될 수 있어 위험하다.
  • prefix 네이밍 — 회사명이나 모듈명을 prefix로 사용하라 (mycompany.email). Spring 내부 키(spring.*)와 충돌을 방지한다.
  • 민감 정보 — 비밀번호, API 키는 yml에 평문으로 두지 말고 환경 변수나 Vault로 주입하라. @ConfigurationProperties는 소스에 상관없이 동일하게 바인딩한다.
  • @NestedConfigurationProperty — 외부 라이브러리 타입을 중첩 속성으로 사용할 때 이 어노테이션을 붙여야 Configuration Processor가 메타데이터를 생성한다.
  • 3rd-party 설정 바인딩@Bean 메서드에 @ConfigurationProperties를 붙이면 외부 클래스에도 바인딩할 수 있다.

정리

@ConfigurationProperties는 단순히 설정값을 읽는 도구가 아니라, 설정의 타입 안전성, 검증, 문서화, 테스트 용이성을 한번에 해결하는 Spring Boot의 핵심 기능이다. Record 바인딩 + JSR-303 검증 + Configuration Processor 조합이면 설정 관련 버그를 시작 시점에 잡을 수 있다. @Value 3개 이상이 보이면 @ConfigurationProperties로 리팩터링을 고려하라.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux