스프링 부트로 서비스 운영하다 보면 “이 기능은 운영에서만 켜자”, “로컬에선 Mock만 쓰자” 같은 요구가 계속 생긴다. 이런 요구를 깔끔하게 처리하는 핵심 도구 중 하나가 바로 @ConditionalOnProperty다.
@ConditionalOnProperty는 설정 파일에 정의된 프로퍼티 값을 보고, 조건이 맞을 때만 빈을 등록한다.
@ConditionalOnProperty(
name = "feature.enabled",
havingValue = "true"
)
feature.enabled=true → 빈 등록 정리하면, “환경 설정 값에 따라 기능을 켜고 끄는 스위치 역할”을 하는 어노테이션이다.
예시로, feature-x라는 기능을 설정으로 제어하는 케이스를 보자.
# application.yml
myapp:
feature-x:
enabled: true
@Configuration
public class FeatureXConfig {
@Bean
@ConditionalOnProperty(
name = "myapp.feature-x.enabled",
havingValue = "true",
matchIfMissing = false
)
public FeatureXService featureXService() {
return new FeatureXService();
}
}
핵심 파라미터만 짚고 넘어간다.
name: 검사할 프로퍼티 이름 havingValue: 이 값과 문자열로 일치할 때 조건 만족 matchIfMissing: 프로퍼티가 아예 없을 때도 조건을 만족시키고 싶다면 true, 기본은 false 이 설정에서는 myapp.feature-x.enabled=true일 때만 FeatureXService가 빈으로 올라온다.
같은 인터페이스에 여러 구현을 두고, 설정 값으로 구현을 고르는 패턴이다.
public interface SessionStore {
String get(String key);
}
@Service
@ConditionalOnProperty(
name = "session.store",
havingValue = "redis"
)
class RedisSessionStore implements SessionStore {
@Override
public String get(String key) {
// Redis 조회 로직
return "...";
}
}
@Service
@ConditionalOnProperty(
name = "session.store",
havingValue = "rdb",
matchIfMissing = true
)
class JdbcSessionStore implements SessionStore {
@Override
public String get(String key) {
// RDB 조회 로직
return "...";
}
}
# application.yml
session:
store: redis # redis / rdb
session.store 값만 바꿔도 세션 저장소 구현이 교체된다.
로컬·테스트에서는 rdb, 운영에서는 redis 같은 식으로 환경별 전략을 설정으로 분리할 수 있다.
외부 결제, 외부 API 호출처럼 실제로는 운영에서만 붙어야 하는 영역에 특히 유용하다.
# application-local.yml
myapp:
payment:
mock-mode: true
public interface PaymentClient {
void pay();
}
@Service
@ConditionalOnProperty(
name = "myapp.payment.mock-mode",
havingValue = "true"
)
class MockPaymentClient implements PaymentClient {
@Override
public void pay() {
System.out.println("Mock payment invoked");
}
}
@Service
@ConditionalOnProperty(
name = "myapp.payment.mock-mode",
havingValue = "false",
matchIfMissing = true
)
class RealPaymentClient implements PaymentClient {
@Override
public void pay() {
// 실제 결제 API 호출
}
}
로컬 프로파일에서 mock-mode=true를, 운영에서는 false 또는 미설정을 두면, 코드 수정 없이 환경별로 Mock / Real 구현이 갈린다.
@Profile과 @ConditionalOnProperty는 목적이 미묘하게 다르다.
@Profile("local") @ConditionalOnProperty(name = "...", havingValue = "...") 같은 계열로 묶어볼 수 있는 조건부 어노테이션들도 있다.
@ConditionalOnClass / @ConditionalOnMissingClass @ConditionalOnBean / @ConditionalOnMissingBean @ConditionalOnProperty는 이 중에서 “환경 설정 파일 기반 기능 스위치” 포지션이라고 보면 된다.@ConditionalOnProperty 한 번 익혀 두면 “이 기능은 운영에서만 쓰자”, “이 구현은 당분간 꺼 두자” 같은 요구를 전부 설정 레벨로 끌어올릴 수 있다. 실제 코드 분기는 어노테이션과 프로퍼티 값이 책임지고, 개발자는 설정만 바꿔서 실험·롤백·A/B 테스트까지 처리하는 구조를 가져갈 수 있다.