[Spring Boot] 외부에서 설정 주입하기

4.2. 외부에서 설정 주입하기

Spring boot 애플리케이션의 설정 값을 외부에서 전달하는 방법을 다룹니다.

기준 버전은 2.2x입니다.

Spring Boot는 동일한 애플리케이션 코드를 다른 환경에서 작업할 수 있도록 설정을 외부에서 전달할 수 있습니다.

사용 가능한 방법은 다음과 같습니다.

  • .properties 파일
  • YAML 파일
  • 환경 변수
  • 커맨드 라인 인자

프로퍼티 값은 @Value 어노테이션을 통해 빈에 직접 주입하거나, Spring의 Environment 추상화를 통해 접근하거나 @ConfigurationProperties를 통해 객체에 바인딩할 수 있습니다.

Spring Boot는 값의 오버라이딩을 구분하도록 설계된 까다로운 순서의 PropertySource를 사용합니다. 프로퍼티는 다음의 순서로 고려됩니다.

  1. 홈 디렉터리(개발 도구가 활성화된 경우 ~/.spring-boot-devtools.properties)의 개발 도구 전역 설정 프로퍼티

  2. 테스트의 @TestPropertySource 어노테이션.

  3. 테스트의 properties 애트리뷰트. @SpringBootTest애플리케이션의 특정 부분을 테스트하기 위한 테스트 어노테이션에서 사용 가능.

  4. 커맨드 라인 인자.

  5. SPRING_APPLICATION_JSON의 프로퍼티(환경 변수나 시스템 프로퍼티에 삽입된 인라인 JSON).

  6. ServletConfig 초기 파라미터.

  7. ServletContext 초기 파라미터.

  8. java:comp/env의 JNDI 애트리뷰트.

  9. Java 시스템 프로퍼티(System.getProperties()).

  10. OS 환경 변수

  11. random.* 에 프로퍼티를 가진RandomValuePropertySource.

  12. 패키지된 jar 외부의 프로파일 지정 애플리케이션 프로퍼티(application-{profile}.properties와 YAML 형식).

  13. 패키지된 jar 내부의 프로파일 지정 애플리케이션 프로퍼티(application-{profile}.properties와 YAML 형식).

  14. 패키지된 jar 외부의 애플리케이션 프로퍼티(application-{profile}.properties와 YAML 형식).

  15. 패키지된 jar 내부의 애플리케이션 프로퍼티(application-{profile}.properties와 YAML 형식).

  16. @Configuration 클래스의 @PropertySource 어노테이션

  17. (SpringApplication.setDefaultProperties에 의해 명시된) 기본 프로퍼티.

구체적인 예를 위해, 다음 예시와 같이 name 프로퍼티를 사용하는 @Component를 개발한다고 가정하겠습니다.

import org.springframework.stereotype.*;
import org.springframework.beans.factory.annotation.*;

@Component
public class MyBean {
    
    @Value("${name}")
    private String name;
    
    // ...
}

(jar 내부와 같은)애플리케이션 클래스패스에서 name의 기본 프로퍼티 값을 제공하는 application.properties을 가질 수 있습니다. 새로운 환경에서 실행할 때, name을 오버라이드하는 application.properties파일을 jar 외부에서 제공할 수 있습니다. 단 한번의 테스트 실행에는 커맨드 라인 스위치(ex. java -jar app.jar --name="Spring")를 명시하여 실행할 수 있습니다.

커맨드라인에서 SPRING_APPLICATION_JSON 프로퍼티를 환경 변수로 사용할 수도 있습니다. 예를 들어, UN*X 쉘에서는 다음과 같이 사용할 수 있습니다:

$ SPRING_APPLICATION_JSON='{"acme":{"name":"test"}}' java -jar myapp.jar

앞의 예시에서 Spring Environtment에서 acme.name=test로 사용해야 했습니다. 다음 예시와 같이 시스템 프로퍼티에서 JSON을 spring.application.json으로 제공할 수도 있습니다:

$ java -Dspring.application.json='{"name":"test"}' -jar myapp.jar

다음과 같이 커맨드 라인 인자로 전달할 수도 있습니다:

java -jar myapp.jar --spring.application.json='{"name":"test"}'

java:comp/env/spring.application.json과 같이 JSON을 JNDI 변수로 전달할 수도 있습니다.

4.2.1 랜덤 값 설정

RandomValuePropertySource는 (ex. 비밀번호 혹은 테스트 케이스에)랜덤 값을 주입할 때 유용합니다. 다음 예시처럼 integer, long, uuid 또는 문자열을 생성할 수 있습니다:

my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
my.uuid=${random.uuid}
my.number.less.than.ten=${random.int(10)}
my.number.in.range=${random.int[1024,65536]}

random.int* 문법은 OPEN value (,max) CLOSE입니다. OPEN, CLOSE는 임의의 문자이며, value, max는 정수이다. max가 있으면, value는 최솟값이 되며 max가 (배타적)최댓값이 됩니다.

4.2.2. 커맨드 라인 프로퍼티 접근

기본적으로, SpringApplication은 (--server.port=9000과 같이 --로 시작하는 인자들)임의의 커맨드 라인 옵션 인자를 property로 변환한 뒤 이를 Spring Environment에 추가합니다. 이전에 언급했듯, 커맨드 라인 프로퍼티는 항상 다른 프로퍼티 소스보다 우선합니다.

커맨드 라인 프로퍼티가 Environment에 추가되는 것을 원치 않으면, SpringApplication.setAddCommandLineProperties(false)를 사용하여 비활성화 할 수 있습니다.

4.2.3. 애플리케이션 프로퍼티 파일

SpringApplication은 다음 경로의 application.properties 에서 프로퍼티를 불러와 Spring Environment에 추가합니다.

  1. 현재 디렉터리의 /config 서브디렉터리
  2. 현재 디렉터리
  3. 클래스패스의 /config 패키지
  4. 클래스패스 루트

위 리스트는 우선순위 순서입니다(위쪽에 위치한 프로퍼티가 아래쪽에 정의된 것을 오버라이드합니다).

’.properties’ 대신 YAML(‘.yml’)파일을 사용할 수도 있습니다.

설정 파일명으로 application.properties가 마음에 들지 않는다면, spring.config.name 환경 프로퍼티에 다른 파일명을 명시하여 변경할 수 있습니다. 또한 spring.config.location 환경 프로퍼티(쉼표로 구분된 디렉터리나 파일 경로의 리스트)로 특정 위치를 참조할 수도 있습니다. 아래 예시는 다른 파일명을 명시하는 방법을 보여줍니다:

$ java -jar myproject.jar --spring.config.name=myproject

다음 예시는 두 경로를 명시하는 방법을 보여줍니다:

$ java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties

spring.config.namespring.config.location은 어떤 파일이 로드돼야 하는지 결정하기 위해 아주 이른 시점에 사용됩니다. 때문에 이들은 환경 프로퍼티(주로 OS 환경 변수, 시스템 프로퍼티 혹은 커맨드 라인 인자)로 정의돼야 합니다.

설정 위치는 역순으로 검색됩니다. 기본값으로 설정된 경로들은 classpath:/,classpath:/config/,file:./,file:./config/입니다. 결과적으로 검색 순서는 다음과 같습니다:

  1. file:./config/
  2. file:./
  3. classpath:/config/
  4. classpath:/

spring.config.location에 별도로 설정 파일 위치가 명시되면 기본 위치값 위치들을 대체합니다. 예를 들어, spring.config.locationclasspath:/custom-config/,file:./custom-config/로 설정되면, 검색 순서는 다음과 같습니다:

  1. file:./custom-config/
  2. classpath:custom-config

대신, spring.config.additional-location으로 설정 파일 위치가 설정되면, 기본 위치들에 추가로 사용됩니다. 추가 위치들은 기본 위치보다 먼저 검색됩니다. 예를 들어, 추가 위치가 classpath:/custom-config/,file:./custom-config/이면, 검색 순서는 다음과 같습니다:

  1. file:./custom-config/
  2. classpath:custom-config/
  3. file:./config/
  4. file:./
  5. classpath:/config/
  6. classpath:/

이러한 검색 순서는 하나의 설정 파일에 기본 값을 명시하고 선택적으로 다른 파일들에서 오버라이드할 수 있도록 합니다. 기본 위치들 중의 하나에application.properties(혹은 spring.config.name에 명시한 다름 이름)로 애플리케이션에 대한 기본 값을 제공할 수 있습니다. 이러한 기본값들은 별도로 명시한 위치들 중의 하나에 위치한 다른 파일을 통해 런타임에 오버라이드될 수 있습니다.

시스템 프로퍼티보다 환경 변수를 사용하는 경우, 대부분의 운영 체제는 마침표로 구분하는 키 이름을 허용하지 않습니다. 대신 언더스코어를 사용할 수 있습니다(ex. spring.config.name 대신 SPRING_CONFIG_NAME).

애플리케이션을 컨테이너에서 실행하는 경우, (java:comp/env의)JNDI 프로퍼티 또는 서블릿 컨텍스트 초기화 파라미터가 환경 변수나 시스템 프로퍼티 대신 사용될 수 있습니다.

4.2.4. 프로파일 명시 프로퍼티

application.properties 파일에 추가로, 프로파일을 명시한 프로퍼티를 다음의 명명 규칙에 따라 정의할 수 있습니다: application-{profile}.properties. Environment은 활성 프로파일이 설정되지 않았을 때 사용되는 기본 프로파일(기본값은 [default]) 집합을 가지고 있습니다. 즉, 명시적으로 활성화된 프로파일이 없으면 application-default.properties이 로드됩니다.

프로파일 명시 프로퍼티는 표준 application.properties과 같은 위치에서 로드되며, 프로파일 구체적인 파일이 패키지된 jar의 내부에 있든 외부에 있든 프로파일 구체적인 파일이 항상 그렇지 않은(프로파일을 명시하지 않은) 파일을 오버라이드합니다.

여러 개의 프로파일이 명시되면, 최후의 승자 전략을 적용합니다. 예를 들어, spring.profiles.active 프로퍼티에 명시된 프로파일들이 SpringApplication API를 통한 설정보다 뒤에 추가되므로 우선권을 갖습니다.

spirng.config.location에 임의의 특정 파일이 있으면, 다른 프로파일 명시 파일은 고려되지 않습니다. 프로파일 명시 프로퍼티도 사용하고자 하는 경우에는 spring.config.location 내의 디렉터리를 사용하세요.

4.2.5. 프로퍼티 플레이스홀더(Placeholder)

application.properties의 값은 사용될 때 기존의 Environment를 통해 필터됩니다. 따라서 (ex. 시스템 프로퍼티 등)이전에 정의된 값을 참조할 수 있습니다.

app.name=MyApp
app.description=${app.name} is a Spring Boot application

이 기법을 기존 Spring Boot 프로퍼티의 “짧은” 변경을 만드는 데에도 사용할 수 있습니다. 자세한 내용은 ‘짧은’ 커맨드 라인 인자 사용을 참고하세요.

4.2.6. 프로퍼티 암호화

Spring Boot는 프로퍼티 값 암호화를 위한 내장 기능을 지원하지 않지만, Spring Environment에 포함된 값을 변경하기 위해 필요한 훅 포인트를 제공합니다. EnvironmentPostProcessor 인터페이스는 애플리케이션을 시작하기 전에 Environment를 조작할 수 있도록 합니다. 자세한 내용은 시작하기 전에 ApplicationContext 또는 Environment 커스터마이즈하기를 참고하세요.

자격 증명이나 패스워드를 저장할 안전한 방법을 찾고 있다면, HashiCorp Vault에 저장된 외부 설정에 대한 지원을 Spring Cloud Vault 프로젝트가 제공하고 있습니다.

4.2.7. 프로퍼티 대신 YAML 사용

YAML은 흔히 말하는 JSON의 수퍼셋으로, 계층 구조의 설정 데이터를 명시하기 편리한 포맷입니다. SpringApplication 클래스는 라이브러리 클래스패스에 SnakeYAML을 가지고 있으면 자동으로 properties 대신 YAML을 지원합니다.

“Starters”를 사용한다면, SnakeYAML은 spring-boot-starter에 자동으로 제공됩니다.

YAML 로딩

Spring 프레임워크는 YAML 문서를 로드하는 데 사용될 수 있는 두 개의 편리한 클래스를 제공합니다. YamlPropertiesFactoryBean은 YAML을 Properties로 로드하고 YamlMapFactoryBean은 YAML을 Map으로 로드합니다.

예를 들어, 다음과 같은 YAML를 가정하겠습니다:

environments:
	dev:
		url: https://dev.example.com
		name: Developer Setup
	prod:
		url: https://another.example.com
		name: My Cool App

앞의 예시는 다음과 같은 프로퍼티로 변환됩니다:

environments.dev.url=https://dev.example.com
environments.dev.name=Developer Setup
environments.prod.url=https://another.example.com
environments.prod.name=My Cool App

YAML 리스트는 [index] 역참조한 프로퍼티 키로 표현됩니다. 예를 들어, 다음과 같은 YAML 문서를 가정하겠습니다:

my:
	servers:
		- dev.example.com
		- another.example.com

앞의 예시는 다음과 같은 프로퍼티로 변환됩니다:

my.servers[0]=dev.example.com
my.servers[1]=another.example.com

(ConfigurationProperties가 하는 것 처럼)Spring Boot의 Binder 유틸리티를 사용하여 이와 같이 프로퍼티를 바인드하려면, 목표 빈에 java.util.List(혹은 Set) 타입의 프로퍼티가 있어야 하며 setter를 제공하거나 가변(mutable) 값으로 초기화해야 합니다. 예를 들어, 다음 예시는 이전에 본 properties와 바인드합니다:

@ConfigurationProperties(prefix="my")
public class Config {
    
    private List<String> servers = new ArrayList<String>();
    
    public List<String> getServers() {
        return this.servers;
    }
}

Spring Environment에 YAML을 프로퍼티로 노출

YamlPropertySourceLoader 클래스는 Spring Environment에서 YAML을 PorpertySource로 노출하는 데 사용할 수 있습니다. 이렇게 함으로써 플레이스홀더 구문으로 @Value 어노테이션을 YAML 프로퍼티에 접근하는 데 사용할 수 있습니다.

다중 프로파일 YAML 문서

다음 예시와 같이 문서를 적용할 시기를 나타내는 spring.profiles키를 사용하여 여러 개의 프로파일을 명시한 YAML 문서를 하나의 파일에 작성할 수 있습니다:

server:
	address: 192.168.1.100
---
spring:
	profiles: development
server:
	address: 127.0.0.1
---
spring:
	profiles: production & eu-central
server:
	address: 192.168.1.120

앞의 예시에서, development 프로파일이 활성화되면, server.address 프로파일은 127.0.0.1입니다. 비슷하게, production eu-central 프로파일이 활성화되면, server.address 프로퍼티는 192.168.1.120입니다. development, productioneu-central 프로파일이 활성화되지 않으면, 프로퍼티 값은 192.168.1.100입니다.

그렇기 때문에 spring.profiles는 단순한 프로파일 이름(ex. production) 또는 프로파일 표현식을 포함할 수 있습니다. 프로파일 표현식은 production & (eu-central | eu-west)과 같이 더 복잡한 프로파일 로직을 표현할 수 있도록 합니다. 더 자세한 내용은 레퍼런스 가이드를 참조하세요.

애플리케이션 컨텍스트를 시작할 때 명시적으로 활성화된 프로파일이 없으면 기본 프로파일이 활성화됩니다. 따라서, 다음의 YAML에서는 오직 “default” 프로파일에서만 사용 가능한 spring.security.user.password 값을 설정합니다:

server:
	port: 8000
---
spring:
	profiles: default
	security:
		user:
			password: weak

반면에, 다음의 예시에서 패스워드는 어떤 프로파일에도 속해있지 않기 때문에 항상 설정되며 필요에 따라 다른 프로파일에서 명시적으로 재설정됩니다:

server:
	port: 8000
spring:
	security:
		user:
			password: weak

spring.profiles 엘리먼트를 사용하여 지정된 Spring 프로파일은 ! 문자를 사용하여 선택적으로 부정될 수 있습니다. 만약 한 문서에 부정된 프로파일과 부정되지 않은 프로파일이 모두 명시되면, 적어도 하나의 부정되지 않은 프로파일이 일치해야 하며, 부정된 프로파일은 일치하지 않을 수 있습니다.

YAML의 단점

YAML 파일은 @PropertySource 어노테이션을 사용해서 로드될 수 없습니다. 때문에 이 방법으로 값을 로드해야 하는 경우 프로퍼티 파일을 사용해야 합니다.

프로파일을 명시한 YAML 파일에서 여러 개의 YAML 문서 구문을 사용하는 것은 예기치 못한 동작으로 이어질 수 있습니다. 예를 들어, application-dev.yml이라는 파일에서 dev 프로파일이 활성화된 상태로 다음 설정을 가정하겠습니다:

server:
	port: 8000
---
spring:
	profiles: !test
	security:
		user:
			password: weak

위의 예시에서, 프로파일 부정과 프로파일 표현식은 예상대로 동작하지 않을 것입니다. 프로파일을 명시한 YAML 파일과 복수의 YAML 문서를 함께 사용하지 않고 둘 중 하나를 계속 사용할 것을 권장합니다.

4.2.8. 타입 안전(Type-safe)한 설정 프로퍼티

설정 프로퍼티 주입에 @Value("${property}") 어노테이션을 사용하는 것은 프로퍼티가 여럿이거나 데이터가 사실상 계층 구조인 상황에서 특히 복잡할 수 있습니다. Spring Boot는 강 타입 빈(strongly-typed beans)이 애플리케이션의 설정을 통제하고 유효한지 검증하도록 하는 방법을 제공합니다.

@Value와 타입 안전한 설정 프로퍼티의 차이도 함께 보세요.

자바 빈 프로퍼티 바인딩

다음 예시처럼 표준 자바 빈 프로퍼티로 선언한 빈을 바인드할 수 있습니다:

package com.example;

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

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

@ConfigurationProperties("acme")
public class AcmeProperties {
    
    private boolean enabled;
    
    private InetAddress remoteAddress;
    
    private final Security security = new Security();
    
    public boolean isEnabled() { ... }
    
    public void setEnabled(boolean enabled) { ... }
    
    public InetAddress getRemoteAddress() { ... }
    
    public void setRemoteAddress(InetAddress remoteAddress) { ... }
    
    public Security getSecurity() { ... }
    
    public static class Security {
        
        private String username;
        
        private String password;
        
        private List<String> roles = new ArrayList<>(Collections.singleton("USER"));
        
        public String getUsername() { ... }
        
        public void setUsername(String username) { ... }
        
        public String getPassword() { ... }
        
        public void setPassword(String password) { ... }
        
        public List<String> getRoles() { ... }
        
        public void setRoles(List<String> roles) { ... }
        
    }
}

앞의 POJO는 다음 프로퍼티를 정의합니다:

  • 기본값이 falseacme.enabled.
  • String으로부터 강제될 수 있는 타입의 acme.remote-address.
  • 프로퍼티의 이름에 따라 이름이 결정되는 중첩된 “security” 객체 acme.security.username. 특히, 반환 타입이 전혀 사용되지 않으며 SecurityProperties이었을 수도 있습니다.
  • acme.security.password.
  • 기본값이 USERString 컬렉션 acme.security.roles.

Spring MVC에서와 같이 바인딩이 표준 자바 빈 프로퍼티 설명자를 통해 이뤄지기 때문에, 이러한 방식은 주로 비어있는 기본 생성자와 게터(getter), 세터(setter)가 필수적입니다. 다음의 경우 세터는 생략될 수도 있습니다:

  • 맵(Map)은 바인더가 변형할 수 있기 때문에 초기화되기만 하면 게터는 필요하지만 세터는 필수가 아닙니다.
  • 컬렉션과 배열은 (주로 YAML에서)인덱스 혹은 단일 쉼표로 구분된 값(프로퍼티)을 통해 접근할 수 있습니다. 후자의 경우 세터가 필수입니다. 이러한 타입에는 항상 세터를 추가할 것을 권장합니다. (앞의 예시에서처럼)컬렉션을 초기화한다면 불변(immutable) 객체가 아닌지 확인하십시오.
  • 중첩된 POJO 프로퍼티가 초기화되면 세터가 필요하지 않습니다. 바인더가 기본 생성자를 사용하여 인스턴스를 바로 생성하려면 세터가 필요합니다.

일부 사람들은 게터와 세터를 자동으로 추가하는 데에 Lombok 프로젝트를 사용하기도 합니다. 컨테이너에서 객체를 인스터화하는데 자동으로 사용되기 때문에, Lombok이 이러한 타입에 대해 특정 생성자를 생성하지 않는지 확인하십시오.

마지막으로, 오직 표준 자바 빈만이 고려되며 정적 프로퍼티에 대한 바인딩은 지원되지 않습니다.

생성자 바인딩

이전 섹션의 예시는 다음 예시와 같이 불변 방식으로 다시 작성할 수 있습니다:

package com.example;

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

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

@ConfigurationProperties("acme")
public class AcmeProperties {
    
    private final boolean enabled;
    
    private final InetAddress remoteAddress;
    
    private final Security security;
    
    public AcmeProperties(boolean enabled, InetAddress remoteAddress, Security security) {
        this.enabled = enabled;
        this.remoteAddress = remoteAddress;
        this.security = security;
    }
    
    public boolean isEnabled() { ... }
    
    public InetAddress getRemoteAddress() { ... }
    
    public Security getSecurity() { ... }
    
    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;
        }
        
        public String getUsername() { ... }
        
        public String getPassword() { ... }
        
        public List<String> getRoles() { ... }
    }
}

이 설정에서, 유일한 생성자는 바인드하고자 하는 프로퍼티의 리스트로 정의돼야 하며 그 외의 다른 프로퍼티는 바인드되지 않습니다.

@DefaultValue를 사용하여 기본 값을 명시할 수 있으며 String 값을 누락된 프로퍼티의 목적 타입으로 강제하는 동일한 변환 서비스가 적용됩니다.

@ConfigurationProperties 어노테이션이 적용된 타입 활성화

Spring Boot는 이러한 타입을 바인드하고 자동으로 빈으로 등록하기 위한 기반을 제공합니다. 만약 애플리케이션이 @SpringBootApplication을 사용한다면, @ConfigurationProperties가 적용된 클래스들을 자동으로 스캔하고 빈으로 등록합니다. 기본적으로, 스캐닝은 이 어노테이션이 선언된 패키지에서부터 수행됩니다. 만약 스캔할 특정 패키지를 정의하려면 다음 예시처럼 SpringBootApplication이 적용된 클래스에 @ConfigurationPropertiesScan 지시자를 명시적으로 사용하십시오:

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

예를 들어 고유한 자동 설정을 개발하는 경우와 같이, 때로는 클래스에 @ConfigurationProperties를 적용하는 것이 스캐닝에 적당하지 않을 수 있습니다. 이런 경우에는 다음 예시와 같이 @Configuration 클래스에 처리할 타입의 리스트를 명시할 수 있습니다:

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

@ConfigurationProperties 빈이 스캐닝이나 @EnableConfigurationProperties를 통해 등록될 때, 빈은 관습적인 이름을 갖습니다: <prefix>-<fqn>, <prefix>@ConfigurationProperties 어노테이션에 명시된 환경 키 접두어가 오고 <fqn>은 정규화된 빈 이름이 옵니다. 어노테이션이 접두어를 따로 제공하지 않으면 정규화된 이름만 가진 빈이 사용됩니다.

위의 예시에서 빈 이름은 acme-com.example.AcmeProperties입니다.

@ConfigurationProperties이 환경만을 다루고, 컨텍스트로부터 다른 빈을 주입하지 않을 것을 권장합니다. 특히 생성자를 사용하여 다른 빈을 주입하는 것은 환경만을 다루는 생성자 바인더를 유발하기 때문에 불가능합니다.

코너 케이스에 대해, 세터 주입이나 (Environment에 접근하는 경우 EnvironmentAware와 같이)프레임워크가 제공하는 *Aware 인터페이스가 사용될 수 있습니다.

클래스패스 스캐닝에서 스캔되는 타입인 경우, 타입에 @ConfigurationProperties@Component와 함께 사용하는 것은 같은 타입의 두 개의 빈을 만듭니다. @Component를 사용하여 빈을 직접 등록하고자 하는 경우, @ConfigurationProperties의 스캐닝을 비활성화하는 것을 고려하십시오.

@ConfigurationProperties가 적용된 타입 사용

이 스타일의 설정은 다음 예시와 같은 SpringApplication 외부 YAML 설정에 특히 효과적입니다:

# application.yml

acme:
	remote-address: 192.168.1.1
	security:
		username: admin
		roles:
			- USER
			- ADMIN

# additional configuration as required

@ConfigurationProperties 빈과 함께 작동하기 위해 다음 예시와 같이 다른 빈과 같은 방법으로 주입할 수 있습니다:

@Service
public class MyService {
    
    private final AcmeProperties properties;
    
    @Autowired
    public MyService(AcmeProperties properties) {
        this.properties = properties;
    }
    
    // ...
    
    @PostConstruct
    public void openConnection() {
        Server server = new Server(this.properties.getRemoteAddress());
        // ...
    }
}

@ConfigurationProperties를 사용하면 IDE가 키를 자동완성 하는 데 사용할 수 있는 메타데이터를 생성하도록 합니다. 자세한 사항은 부록을 참고하세요.

서드 파티 설정

@ConfigurationProperties를 클래스에 사용하는 것과 마찬가지로, 이를 public @Bean 메서드에도 사용할 수 있습니다. 이는 통제 밖에 있는 서드 파티 컴포넌트를 프로퍼티에 바인드하고자 할 때 유용합니다.

다음 예시와 같이 Environment 프로퍼티로부터 빈을 설정하기 위해 빈 등록을 위한 @ConfigurationProperties를 추가합니다:

@ConfigurationProperties(prefix = "another")
@Bean
public AnotherComponent anotherComponent() {
    ...
}

another 접두어로 정의된 자바 빈 프로퍼티는 앞의 AcmeProperties 예시와 유사한 방식으로 AnotherComponent 빈에 매핑됩니다.

완화된 바인딩(Relaxed Binding)

Spring Boot는 Environment 프로퍼티를 @ConfigurationProperties 빈에 바인딩하기 위해 완화된 규칙을 사용하기 때문에 Environment 프로퍼티 이름과 빈 프로퍼티 이름이 정확히 일치할 필요가 없습니다. 대시(-)로 구분된 환경 프로퍼티(ex. context-pathcontextPath에 바인드)와 대문자로 작성(capitalized)된 환경 프로퍼티(ex. PORTport에 바인드)는 이것이 유용한 흔한 예입니다.

예를 들어, 다음과 같은 @ConfigurationProperties 클래스를 가정합니다:

@ConfigurationProperties(prefix = "acme.my-project.person")
public class OwnerProperties {
    
    private String firstName;
    
    public String getFirstName() {
        return this.firstName;
    }
    
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
}

앞의 예시는 다음의 프로퍼티 이름이 모두 사용될 수 있습니다:

프로퍼티참고
acme.my-project.person.first-nameproperties.yml 파일에 사용하도록 권장되는 케밥 케이스.
acme.myProject.person.firstName표준 카멜 케이스 문법.
acme.my_project.person.first_name.properties.yml 파일에서 대체 형식으로 사용되는 언더스코어 표기법.
ACME_MYPROJECT_PERSON_FIRSTNAME시스템 환경 변수를 사용할 때 권장되는 대문자 형식.

어노테이션에 대한 prefix 값은 (acme.my-project.person과 같이 소문자이며 -로 구분된)케밥 케이스*여야*합니다.

프로퍼티 소스단순리스트
프로퍼티 파일카멜 케이스, 케밥 케이스 또는 언더스코어 표기법[ ] 또는 값을 쉼표로 구분한 표준 리스트 구문
YAML 파일카멜 케이스, 케밥 케이스 또는 언더스코어 표기법표준 YAML 리스트 구분 또는 쉼표로 구분한 값
환경 변수구분자로 언더스코어를 사용한 대문자 형식. _는 프로퍼티 이름에서 사용되면 안됨MY_ACME_1_OTHER = my.acme[1].other와 같이 언더스코어에 둘러싸인 숫자 값
시스템 프로퍼티카멜 케이스, 케밥 케이스 또는 언더스코어 표기법[ ] 또는 값을 쉼표로 구분한 표준 리스트 구문

가능한 경우 프로퍼티는 my.property-name=acme와 같이 소문자 케밥 케이스 사용을 권장합니다.

Map 프로퍼티를 바인딩할 때, key가 소문자 알파벳과 숫자 또는 - 이외의 다른 문자를 포함하면, 원래의 값이 보존되도록 브라켓 표기법을 사용해야 합니다. 키가 []에 둘러싸이지 않으면, 알파벳과 숫자 또는 -이 아닌 문자는 지워집니다. 예를 들어 다음 프로퍼티를 Map에 바인딩한다고 가정하면:

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

위의 프로퍼티는 Map/key1, /key2, key3로 바인드됩니다.

복합 타입 합치기

리스트가 한 군데 이상에 구성된 경우, 오버라이딩은 전체 리스트를 대체하는 형태로 동작합니다.

기본값이 nullnamedescription 속성을 가진 MyPojo 객체를 에로 들면, 다음 예시는 AcmeProperties로부터 MyPojo 객체의 리스트를 노출합니다:

@ConfigurationProperties("acme")
public class AcmeProperties {
    
    private final List<MyPojo> list = new ArrayList<>();
    
    public List<MyPojo> getList() {
        return this.list;
    }
    
}

다음 설정을 함께 적용합니다:

acme:
	list:
		- name: my name
		  description: my description
---
spring:
	profiles: dev
acme:
	list:
		- name: my another name

dev 프로파일이 활성화되지 않은 경우, 앞에서 정의된대로 AcmeProperties.listMypojo 엔트리 하나를 갖습니다. 하지만 dev 프로파일이 활성화되면, list여전히 (my another name이라는 이름과 null인 설명을 갖는) 엔트리 하나만을 갖습니다. 이 설정은 두 번째 MyPojo 인스턴스를 리스트에 추가*하지 않으*며, 아이템을 합치지 않습니다.

List가 여러 프로파일에서 명시되면, (오직 하나뿐인)가장 높은 우선순위를 가진 하나만이 사용됩니다. 다음 예시를 보겠습니다:

acme:
	list:
		- name: my name
		  description: my description
		- name: another name
		  description: another description
---
spring:
	profiles: dev
acme:
	list:
		- name: my another name

앞의 예시에서 dev 프로파일을 활성화하면, AcmeProperties.list는 (이름이 my another name이고 설명이 null인)하나의 MyPojo 엔트리를 갖습니다. YAML에서는 리스트의 내용을 오버라이딩하는데 쉼표로 구분된 리스트와 YAML 리스트를 사용할 수 있습니다.

Map 프로퍼티에는 여러 소스로부터 프로퍼티 값을 뽑아 바인드할 수 있습니다. 하지만, 여러 소스의 같은 프로퍼티에 대해서는 우선순위가 가장 높은 하나가 사용됩니다. 다음의 예시는 AcmeProperties로부터 Map<String, MyPojo>를 노출합니다:

@ConfigurationProperties("acme")
public class AcmeProperties {
    
    private final Map<String, MyPojo> map = new HashMap<>();
    
    public Map<String, MyPojo> getMap() {
        return this.map;
    }
}

다음의 설정을 적용합니다:

acme:
	map:
		key1:
			name: my name 1
			description: my description 1
---
spring:
	profiles: dev
acme:
	map:
		key1:
			name: dev name 1
		key2:
			name: dev name 2
			description: dev description 2

dev 프로파일을 활성화하지 않으면, AcmeProperties.map은 (name이 my name 1이고 description이 my description인)key1인 엔트리 하나를 갖습니다. dev 프로파일을 활성화하면 map은 (name이 dev name 1이고 description이 my description 1인)key1과 (name이 dev name 2이고 description이 dev description 2인)key2를 키로 하는 두 개의 엔트리를 갖습니다.

앞서 다룬 병합 규칙은 YAML 파일 뿐만 아니라 모든 프로퍼티 소스의 프로퍼티에 적용합니다.

프로퍼티 변환

외부 애플리케이션 프로퍼티가 @ConfigurationProperties 빈에 바인드되어 있으면 Spring Boot는 강제 변환을 시도합니다. 커스텀 타입 변환이 필요한 경우, (conversionService라는 이름의)ConversionService 빈 또는 (CustomeEditorConfiguerer 빈을 통한)커스텀 프로퍼티 에디터 또는 커스텀 (@ConfigurationPropertiesBinding 어노테이션이 사용된 빈을 정의하여)Converters를 제공할 수 있습니다.

이 빈은 애플리케이션 생명주기에서 매우 이른 시점에 요청되기 때문에 ConversionService가 사용하는 의존성을 반드시 제한하십시오. 보통, 의존성은 생성 시점에 완전히 초기화되어있지 않을 수 있습니다. 설정 키의 변환에 필요하지 않고 오직 @ConfigurationPropertiesBinding으로 지정된 커스텀 컨버터에만 의존하는 경우에는 커스텀 ConversionService의 이름을 바꾸고자 할 수도 있습니다.

시간(duration) 변환

Spring Boot는 시간 표현을 지원합니다. java.time.Duration 프로퍼티를 노출하면, 애플리케이션 프로퍼티에서 다음 형식을 사용할 수 있습니다:

  • (@DurationUnit이 명시되지 않으면 밀리초를 기본 단위로 사용하는) long 표기
  • java.time.Duration에서 사용하는 ISO-8601 표준 형식
  • 값과 단위를 붙여서 읽을 수 있는 형식 (e.g. 10s는 10초를 의미)

다음 예시를 보겠습니다:

@ConfigurationProperties("app.system")
public class AppSystemProperties {
    
    @DurationUnit(ChronoUnit.SECONDS)
    private Duration sessionTimeout = Duration.ofSeconds(30);
    
    private Duration readTimeout = Duration.ofMillis(1000);
    
    public Duration getSessionTimeout() {
        return this.sessionTimeout;
    }
    
    public void setSessionTimeout(Duration sessionTimeout) {
        this.sessiontTimeout = sessionTimeout;
    }
    
    public Duration getReadTimeout() {
        return this.readTimeout;
    }
    
    public void setReadTimeout(Duration readTimeout) {
        this.readTimeout = readTimeout;
    }

}

세션 타임아웃 시간을 30 초로 지정할 때, 30, PT30S30s는 모두 같습니다. 읽기 타임아웃을 500ms로 지정할 때는 다음과 같은 형식을 사용할 수 있습니다: 500, PT0.5S500ms.

지원되는 단위는 다음과 같습니다:

  • ns: 나노 초
  • us: 마이크로 초
  • ms: 밀리 초
  • s: 초
  • m: 분
  • h: 시
  • d: 일(day)

기본 단위는 밀리 초이며 위의 예시에서 본 것과 같이 @DurationUnit을 사용하여 재정의할 수 있습니다.

시간 표현에 Long을 사용하는 이전 버전에서 업그레이드하는 중인 경우, 단위가 밀리 초가 아닌 경우에는 (@DurationUnit을 사용하여) 단위를 분명히 정의하십시오. 이렇게 함으로써 더 풍부한 형식을 지원하면서 투명한 업그레이드 경로를 제시합니다.

데이터 크기 변환

Spring Framework에는 바이트 크기를 표현할 수 있는 DataSize 값 타입이 있습니다. DataSize 프로퍼티를 노출하면, 애플리케이션 프로퍼티에서 다음 형식을 사용할 수 있습니다:

  • (@DataSizeUnit이 명시되지 않은 한 바이트를 기본 단위로 사용하는) long 표기
  • 값과 단위를 붙여서 읽을 수 있는 형식 (e.g. 10MB는 10 메가바이트를 의미)

다음 예시를 보겠습니다:

@ConfigurationProperties("app.io")
public class AppIoProperties {
    
    @DataSizeUnit(DataUnit.MEGABYTES)
    private DataSize bufferSize = DataSize.ofMegabytes(2);
    
    private DataSize sizeTHreashold = DataSize.ofBytes(512);
    
    public DataSize getBufferSize() {
        return this.bufferSize;
    }
    
    public void setBufferSize(DataSize bufferSize) {
        this.bufferSize = bufferSize;
    }
    
    public DataSize getSizeThreshold() {
        return this.sizeThreshold;
    }
    
    public void setSizeThreshold(DataSize sizeThreshold) {
        this.sizeThreshold = sizeThreshold;
    }
}

버퍼 크기를 10 메가바이트로 지정할 때, 1010MB는 같습니다. 256 바이트의 크기 임계치는 256 또는 256B로 명시할 수 있습니다.

지원되는 단위는 다음과 같습니다:

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

기본 단위는 바이트이며 위의 예시에서 본 것과 같이 @DataSizeUnit을 사용하여 재정의할 수 있습니다.

크기 표현에 Long을 사용하는 이전 버전에서 업그레이드하는 중인 경우, 단위가 바이트가 아닌 경우에는 (@DataSizeUnit을 사용하여) 단위를 분명히 정의하십시오. 이렇게 함으로써 더 풍부한 형식을 지원하면서 투명한 업그레이드 경로를 제시합니다.

@ConfigurationProperties 유효성 검사(validation)

@ConfigurationProperties 클래스에 @Validated 어노테이션이 사용되면 Sprint Boot는 유효성 검사를 시도합니다. JSR-303 javax.validation 제약 조건 어노테이션을 설정 클래스에 바로 사용할 수 있습니다. 이를 위해, 클래스패스에 JSR-303을 준수하는 구현이 있는지 확인하고, 다음 예시와 같이 제약 조건 어노테이션을 필드에 추가하십시오:

@ConfigurationProperties(prefix="acme")
@Validated
public class AcmeProperties {
    
    @NotNull
    private InetAddress remoteAddress;
    
    // ... getters and setters
}

설정 프로퍼티를 생성하는 @Bean 메서드에 @Validated를 사용해서 유효성 검사를 발동할 수도 있습니다.

중첩 프로퍼티도 바인드될 때 유효성 검사를 하지만, 연관된 필드에도 @Valid 어노테이션을 사용하는 것이 좋은 습관입니다. 이는 중첩 프로퍼티를 찾을 수 없는 경우에도 유효성 검사를 발동하도록 보장합니다. 다음 예시는 앞선 AcmeProperties 예시를 기반으로 합니다:

@ConfigurationProperties(prefix="acme")
@Validated
public class AcmeProperties {
    
    @NotNull
    private InetAddress remoteAddress;
    
    @Valid
    private final SEcurity security = new Security();
    
    // ... getters and setters
    
    public static class Security {
        
        @NotEmpty
        public String username;
        
        // ... getters and setters
        
    }
}

configurationPropertiesValidator로 호출되는 빈 정의를 생성하여 커스텀 Spring Validator를 추가할 수도 있습니다. @Bean 메서드는 static으로 선어되어야 합니다. 설정 프로퍼티 유효성 검사자(validator)는 애플리케이션 생명 주기에서 매우 이른 시기에 생성되며, @Bean 메서드를 정적으로 선언하면 @Configuration 클래스를 인스턴스화할 필요 없이 빈을 생성하도록 합니다. 이렇게 함으로써 이른 시기에 인스턴스화하여 발생할 수 있는 문제들을 피합니다.

spring-boot-actuator 모듈은 모든 @ConfiruationProperties 빈들을 노출하는 엔드포인트를 갖고 있습니다. 웹 브라우저로 /actuator/configprops에 접속하거나 동일한 JMX 엔드포인트를 사용하세요. 자세한 사항은 “Productrion ready features“를 참조하세요.

@ConfigurationProperties vs. @Value

@Value 어노테이션는 핵심 컨테이너 기능이며, 타입 안전한 설정 프로퍼티와 동일한 기능을 제공하지 않습니다. 다음 표는 @ConfigurationProperties@Value가 지원하는 기능들을 요약합니다:

기능@ConfigurationProperties@Value
완화된 바인딩YesNo
메타-데이터 지원YesNo
SpEL 평가NoYes

고유 컴포넌트를 위한 설정 키 집합을 정의할 때는 이들을 @ConfigurationProperties 어노테이션을 사용하는 POJO로 그룹화할 것을 권장합니다. @Value는 완화된 바인딩을 지원하지 않으므로, 환경 변수를 사용하여 값을 제공해야 하는 경우에는 좋은 선택지가 아니라는 점을 유념하십시오.

마지막으로, @Value에서는 SpEL 표현식을 사용할 수 있는 반면에, 이러한 표현식은 애플리케이션 프로퍼티 파일로부터 처리되지 않습니다.

목록으로