[Spring] @Conditional, Profile, @ConfigurationProperties
정의
Spring Boot는 조건부 Bean 등록 메커니즘을 제공한다. classpath, property, 다른 Bean 유무 등에 따라 자동 구성을 활성화/비활성화. @Profile, @ConditionalOnX 어노테이션이 핵심.
@Profile
환경별 다른 Bean.
@Configuration
public class DataConfig {
@Bean
@Profile("dev")
public DataSource devDataSource() {
return new EmbeddedDatabaseBuilder().build();
}
@Bean
@Profile("prod")
public DataSource prodDataSource() {
// 진짜 DB
...
}
@Bean
@Profile("!test")
public EmailService realEmailService() {
return new SmtpEmailService();
}
@Bean
@Profile({"dev", "test"})
public EmailService mockEmailService() {
return new LoggingEmailService();
}
}
활성 프로파일에 매칭되는 Bean만 등록.
# application.yml
spring:
profiles:
active: prod
---
spring:
config:
activate:
on-profile: dev
custom:
feature: enabled
spring.profiles.active=prod 또는 SPRING_PROFILES_ACTIVE=prod.
@ConditionalOnX (Spring Boot)
@ConditionalOnClass / @ConditionalOnMissingClass
@Configuration
@ConditionalOnClass(name = "com.zaxxer.hikari.HikariDataSource")
public class HikariConfig {
@Bean
public DataSource hikariDataSource() {
return new HikariDataSource(...);
}
}
classpath에 HikariCP가 있을 때만.
@ConditionalOnBean / @ConditionalOnMissingBean
@Bean
@ConditionalOnMissingBean // 사용자가 정의 안 했으면 기본 Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
Spring Boot 자동 구성의 핵심 패턴. 사용자가 같은 타입 정의하면 자동 양보.
@ConditionalOnProperty
@Bean
@ConditionalOnProperty(name = "app.feature.enabled", havingValue = "true")
public FeatureService featureService() { ... }
@Bean
@ConditionalOnProperty(name = "spring.cache.type", havingValue = "redis", matchIfMissing = false)
public RedisCacheManager redisCacheManager() { ... }
@Bean
@ConditionalOnProperty(prefix = "app", name = "auth-method", havingValue = "jwt")
public JwtAuthFilter jwtAuthFilter() { ... }
property 값 조건. 신규 기능 토글, 환경별 분기.
@ConditionalOnExpression (SpEL)
@Bean
@ConditionalOnExpression("${app.feature.enabled:false} and '${spring.profiles.active}' != 'test'")
public FeatureService featureService() { ... }
복잡한 조건.
@ConditionalOnWebApplication / @ConditionalOnNotWebApplication
@Bean
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public ServletBean servletBean() { ... }
@Bean
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public ReactiveBean reactiveBean() { ... }
@ConditionalOnJava
@Bean
@ConditionalOnJava(JavaVersion.SEVENTEEN) // Java 17+
public ModernBean modernBean() { ... }
@ConditionalOnResource
@Bean
@ConditionalOnResource(resources = "classpath:my-config.json")
public MyConfig myConfig() { ... }
특정 리소스 파일 존재 시.
커스텀 Conditional
public class OnLinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return System.getProperty("os.name").toLowerCase().contains("linux");
}
}
@Bean
@Conditional(OnLinuxCondition.class)
public LinuxService linuxService() { ... }
@ConfigurationProperties
타입 안전 외부 설정.
기본
@ConfigurationProperties(prefix = "app")
public record AppProperties(
String name,
Database database,
int timeout,
List<String> tags,
Map<String, String> features
) {
public record Database(
String url,
String username,
String password,
int poolSize
) { }
}
app:
name: myapp
timeout: 30
tags:
- production
- east
features:
new-ui: "true"
legacy-api: "false"
database:
url: jdbc:postgresql://localhost/myapp
username: app
password: secret
pool-size: 10
활성화
@SpringBootApplication
@EnableConfigurationProperties(AppProperties.class)
public class App { }
// 또는 @ConfigurationPropertiesScan
@ConfigurationPropertiesScan("com.example.config")
사용
@Service
public class MyService {
private final AppProperties props;
public MyService(AppProperties props) {
this.props = props;
}
public void doIt() {
if (props.features().get("new-ui").equals("true")) {
...
}
}
}
검증
@ConfigurationProperties(prefix = "app")
@Validated
public record AppProperties(
@NotBlank String name,
@Min(0) @Max(600) int timeout,
@Valid Database database
) {
public record Database(
@NotBlank String url,
@Min(1) int poolSize
) { }
}
시작 시 검증 실패 → 시작 실패. 환경 잘못 발견 즉시.
관계형 properties
app:
servers:
- host: server1.example.com
port: 8080
- host: server2.example.com
port: 8081
@ConfigurationProperties(prefix = "app")
public record AppProperties(List<Server> servers) {
public record Server(String host, int port) { }
}
meta-data
IDE 자동완성을 위해 additional-spring-configuration-metadata.json 또는 annotation processor:
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
자동으로 META-INF/spring-configuration-metadata.json 생성. IntelliJ에서 yaml 자동완성.
자주 보는 패턴
Feature flag
@ConfigurationProperties("app.features")
public record FeatureFlags(
boolean newDashboard,
boolean aiSuggestions,
boolean betaPayments
) { }
app:
features:
new-dashboard: true
ai-suggestions: false
beta-payments: true
@Service
public class DashboardService {
private final FeatureFlags features;
public Dashboard render() {
return features.newDashboard() ? renderNew() : renderClassic();
}
}
런타임 변경은 별도 (Flagsmith, Unleash, LaunchDarkly).
환경별 Bean
@Configuration
@Profile("prod")
public class ProductionConfig {
@Bean public ExternalApi externalApi() { ... }
}
@Configuration
@Profile({"dev", "test"})
public class MockConfig {
@Bean public ExternalApi mockApi() { ... }
}
@ConfigurationProperties Bean
@ConfigurationProperties("aws")
public class AwsProperties {
private String accessKey;
private String secretKey;
private String region;
// setter 필요 (lombok @Setter)
}
@SpringBootApplication
@EnableConfigurationProperties(AwsProperties.class)
public class App { }
record (3.0+)가 더 간결.
함정
1. @Value vs @ConfigurationProperties
@Value("${app.name}")
private String name;
vs
@ConfigurationProperties("app")
public record AppProperties(String name) { }
10개 이상 또는 nested → properties. 1-2개면 @Value도 OK.
2. property naming
public record AppProperties(String apiKey) { } // Java camelCase
app:
api-key: ... # kebab-case 권장
apiKey: ... # camelCase OK
api_key: ... # snake_case OK (relaxed binding)
여러 표기 자동 매핑. yaml은 kebab-case가 convention.
3. Conditional 평가 시점
@ConditionalOnProperty는 시작 시 한 번 평가. 런타임 property 변경엔 영향 X.
4. @ConfigurationProperties + immutable
public record AppProperties(String name) { } // record OK
public class AppProperties { // mutable
@Setter @Getter String name;
}
record는 setter 없어 @ConfigurationProperties 사용 시 컨스트럭터 주입.
5. 시크릿을 properties에
app:
api:
key: ${API_KEY} # ENV 참조 → OK
key: hardcoded # 위험 (코드에 비밀)
비밀은 항상 환경변수 또는 secrets manager 통해.
Auto-configuration 만들기
@AutoConfiguration
@ConditionalOnClass(MyService.class)
@EnableConfigurationProperties(MyProperties.class)
public class MyAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public MyService myService(MyProperties props) {
return new MyService(props.url());
}
}
src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.MyAutoConfiguration
라이브러리 사용자가 dependency 추가만으로 자동 활성화.
이 개념을 다룬 위키 페이지 (7)
- wiki[Spring] @Autowired 해결 전략: @Qualifier, @Primary, byType
- wiki[Spring] Bean Lifecycle: 초기화와 소멸 콜백
- wiki[Spring] BeanPostProcessor와 BeanFactoryPostProcessor
- wiki[Spring Boot] Properties 외부화: PropertySource, Profile, @ConfigurationProperties
- wiki[Spring Boot] YAML Configuration: multi-document, profiles, anchors
- wiki[Spring Boot] Hibernate Naming Strategy
- wiki[Spring] IoC와 DI: Bean, ApplicationContext, @Autowired
💬 댓글