Spring @ConditionalOnProperty란?
Spring Boot 자동 설정의 핵심은 조건부 빈 등록이다. @ConditionalOnProperty는 설정 파일의 특정 프로퍼티 값에 따라 빈을 등록하거나 건너뛴다. 이를 활용하면 코드 변경 없이 설정만으로 기능을 켜고 끌 수 있다.
# application.yml
app:
cache:
enabled: true
notification:
channel: slack
// cache.enabled=true일 때만 빈 등록
@Configuration
@ConditionalOnProperty(
prefix = "app.cache",
name = "enabled",
havingValue = "true",
matchIfMissing = false // 프로퍼티 없으면 빈 미등록
)
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
return new CaffeineCacheManager();
}
}
핵심 속성 4가지
| 속성 | 설명 | 예시 |
|---|---|---|
prefix |
프로퍼티 접두사 | "app.cache" |
name |
프로퍼티 이름 (prefix와 결합) | "enabled" → app.cache.enabled |
havingValue |
매칭할 값 (생략 시 존재만 확인) | "true", "slack" |
matchIfMissing |
프로퍼티 없을 때 매칭 여부 | true = 기본 활성화 |
실전 패턴: Feature Toggle
기능 플래그를 프로퍼티로 관리하는 가장 실용적인 패턴이다:
# application.yml
features:
new-pricing: true
beta-search: false
export-pdf: true
// 새 가격 정책 활성화 시에만 등록
@Service
@ConditionalOnProperty(name = "features.new-pricing", havingValue = "true")
public class NewPricingService implements PricingService {
public BigDecimal calculate(Order order) {
// 새 가격 로직
}
}
// 비활성화 시 기존 서비스 사용
@Service
@ConditionalOnProperty(name = "features.new-pricing", havingValue = "false",
matchIfMissing = true)
public class LegacyPricingService implements PricingService {
public BigDecimal calculate(Order order) {
// 기존 가격 로직
}
}
핵심: 같은 인터페이스의 두 구현체를 @ConditionalOnProperty로 토글한다. 설정만 바꾸면 코드 배포 없이 기능을 전환할 수 있다.
알림 채널 전환
public interface NotificationSender {
void send(String userId, String message);
}
@Service
@ConditionalOnProperty(name = "app.notification.channel", havingValue = "slack")
public class SlackNotificationSender implements NotificationSender {
public void send(String userId, String message) {
// Slack API 호출
}
}
@Service
@ConditionalOnProperty(name = "app.notification.channel", havingValue = "email")
public class EmailNotificationSender implements NotificationSender {
public void send(String userId, String message) {
// 이메일 발송
}
}
@Service
@ConditionalOnProperty(name = "app.notification.channel", havingValue = "noop",
matchIfMissing = true)
public class NoopNotificationSender implements NotificationSender {
public void send(String userId, String message) {
// 아무것도 안 함 (개발/테스트 환경)
}
}
다른 @Conditional 어노테이션들
Spring Boot는 다양한 조건부 어노테이션을 제공한다:
// 클래스패스에 특정 클래스가 있을 때만
@ConditionalOnClass(RedisConnectionFactory.class)
public class RedisConfig { ... }
// 특정 빈이 없을 때만 (기본 구현 제공)
@Bean
@ConditionalOnMissingBean(CacheManager.class)
public CacheManager defaultCacheManager() {
return new ConcurrentMapCacheManager();
}
// 특정 빈이 존재할 때만
@Bean
@ConditionalOnBean(DataSource.class)
public JdbcTemplate jdbcTemplate(DataSource ds) {
return new JdbcTemplate(ds);
}
// 특정 프로파일일 때만
@Configuration
@ConditionalOnProperty(name = "spring.profiles.active", havingValue = "prod")
// 또는 더 단순하게:
@Profile("prod")
public class ProdConfig { ... }
조건 조합
// 여러 조건 AND 결합 (모두 만족해야 등록)
@Configuration
@ConditionalOnProperty(name = "app.cache.enabled", havingValue = "true")
@ConditionalOnClass(RedisConnectionFactory.class)
@ConditionalOnBean(RedisConnectionFactory.class)
public class RedisCacheConfig {
// cache 활성화 + Redis 클래스 존재 + Redis 빈 존재 시에만
}
Custom Starter에서의 활용
Custom Starter의 자동 설정에서 @ConditionalOnProperty는 필수다:
@AutoConfiguration
@EnableConfigurationProperties(MyLibProperties.class)
@ConditionalOnProperty(
prefix = "mylib",
name = "enabled",
havingValue = "true",
matchIfMissing = true // 기본 활성화
)
public class MyLibAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 사용자가 직접 정의하면 스킵
public MyService myService(MyLibProperties props) {
return new DefaultMyService(props);
}
@Bean
@ConditionalOnProperty(name = "mylib.metrics.enabled", havingValue = "true")
public MyMetricsCollector metricsCollector() {
return new MyMetricsCollector();
}
}
matchIfMissing = true와 @ConditionalOnMissingBean의 조합이 핵심이다. 라이브러리는 기본적으로 동작하되, 사용자가 커스터마이즈할 수 있는 유연한 구조를 제공한다.
커스텀 Condition 작성
기본 제공 어노테이션으로 부족하면, Condition 인터페이스를 직접 구현한다:
// 환경 변수 기반 조건
public class OnKubernetesCondition implements Condition {
@Override
public boolean matches(ConditionContext context,
AnnotatedTypeMetadata metadata) {
return System.getenv("KUBERNETES_SERVICE_HOST") != null;
}
}
@Configuration
@Conditional(OnKubernetesCondition.class)
public class K8sConfig {
// K8s 환경에서만 등록
}
테스트에서 조건부 빈 검증
@SpringBootTest(properties = "features.new-pricing=true")
class NewPricingTest {
@Autowired
private PricingService pricingService;
@Test
void shouldUseNewPricing() {
assertThat(pricingService).isInstanceOf(NewPricingService.class);
}
}
@SpringBootTest(properties = "features.new-pricing=false")
class LegacyPricingTest {
@Autowired
private PricingService pricingService;
@Test
void shouldUseLegacyPricing() {
assertThat(pricingService).isInstanceOf(LegacyPricingService.class);
}
}
// ApplicationContextRunner로 가벼운 테스트
@Test
void shouldRegisterCacheWhenEnabled() {
new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(CacheConfig.class))
.withPropertyValues("app.cache.enabled=true")
.run(context -> {
assertThat(context).hasSingleBean(CacheManager.class);
});
}
환경별 설정 전략
Spring Profiles와 @ConditionalOnProperty를 조합하면 환경별로 다른 빈을 깔끔하게 관리할 수 있다:
# application-dev.yml
app:
storage:
type: local
notification:
channel: noop
# application-prod.yml
app:
storage:
type: s3
notification:
channel: slack
개발 환경에서는 로컬 스토리지와 Noop 알림, 프로덕션에서는 S3와 Slack을 자동으로 사용한다.
정리
@ConditionalOnProperty는 Spring Boot의 설정 기반 빈 등록 핵심이다. Feature Toggle, 알림 채널 전환, 환경별 구현체 교체 등을 코드 변경 없이 설정만으로 제어할 수 있다. @ConditionalOnMissingBean, @ConditionalOnClass와 조합하면 Custom Starter의 유연한 자동 설정까지 구현할 수 있다.