1. Type-Safe Configuration Properties 소개
@Value("${property}") 어노테이션을 사용한 설정 프로퍼티 주입은 여러 프로퍼티를 다루거나 계층적 데이터 구조를 가진 경우 번거로울 수 있습니다. Spring Boot는 강타입 빈을 통해 애플리케이션 설정을 관리하고 검증할 수 있는 대안적인 방법을 제공합니다.
- 타입 안전성: 컴파일 타임에 타입 오류를 검출할 수 있습니다.
- 구조화된 설정: 관련된 설정들을 하나의 클래스로 그룹화할 수 있습니다.
- 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- 기본값 falsemy.service.remote-address- String에서 변환 가능한 타입my.service.security.username- 중첩된 "security" 객체my.service.security.passwordmy.service.security.roles- 기본값 USER를 가진 String 컬렉션
이러한 방식은 기본 빈 생성자와 getter/setter 메서드에 의존합니다. 바인딩은 Spring MVC와 동일하게 표준 Java Beans 프로퍼티 디스크립터를 통해 수행됩니다.
2.3 Setter 생략 가능한 경우
다음 경우에는 setter를 생략할 수 있습니다:
- Map: 초기화되어 있다면 getter만 필요하고 setter는 필수가 아닙니다
- 컬렉션과 배열: 인덱스나 쉼표로 구분된 값으로 접근 가능합니다
- 중첩 POJO: 초기화되어 있다면 setter가 필요하지 않습니다
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 기반 프로퍼티 바인딩을 사용하세요.