Spring @Conditional 자동 설정

Auto-Configuration이란?

Spring Boot의 마법 같은 “설정 없이 동작”은 Auto-Configuration 덕분이다. 클래스패스에 라이브러리가 있으면 자동으로 Bean을 등록하고, application.yml 프로퍼티로 동작을 튜닝한다. 이 메커니즘의 핵심이 @Conditional 어노테이션 패밀리다. 자체 스타터 라이브러리를 만들거나 조건부 Bean 등록을 제어하려면 반드시 이해해야 한다.

@Conditional 어노테이션 종류

어노테이션 조건 예시
@ConditionalOnClass 클래스패스에 특정 클래스 존재 Redis 라이브러리가 있을 때만
@ConditionalOnMissingClass 클래스패스에 특정 클래스 없음 폴백 구현 등록
@ConditionalOnBean 특정 Bean이 이미 등록됨 DataSource가 있을 때만
@ConditionalOnMissingBean 특정 Bean이 없음 사용자 커스텀 Bean 우선
@ConditionalOnProperty 프로퍼티 값 조건 feature flag 활성화 시
@ConditionalOnExpression SpEL 표현식 평가 복합 조건
@ConditionalOnResource 리소스 파일 존재 설정 파일이 있을 때만
@ConditionalOnWebApplication 웹 애플리케이션 타입 Servlet/Reactive 구분

@ConditionalOnProperty 심화

가장 자주 사용되는 조건이다. Feature Flag, 환경별 설정 분기에 핵심적이다.

// 기본 사용: 프로퍼티가 "true"일 때만 Bean 등록
@Configuration
@ConditionalOnProperty(name = "app.cache.enabled", havingValue = "true")
public class CacheConfig {
    @Bean
    public CacheManager cacheManager() {
        return new RedisCacheManager(...);
    }
}

// matchIfMissing: 프로퍼티가 없어도 기본 활성화
@ConditionalOnProperty(
    name = "app.metrics.enabled",
    havingValue = "true",
    matchIfMissing = true  // 프로퍼티 미설정 시 true로 간주
)
public class MetricsConfig { }

// prefix로 그룹화
@ConditionalOnProperty(
    prefix = "app.notification",
    name = "type",
    havingValue = "slack"
)
public class SlackNotificationConfig { }

@ConditionalOnProperty(
    prefix = "app.notification",
    name = "type",
    havingValue = "email"
)
public class EmailNotificationConfig { }
# application.yml
app:
  cache:
    enabled: true
  notification:
    type: slack  # slack → SlackNotificationConfig 활성화
  metrics:
    # 미설정 → matchIfMissing=true이므로 MetricsConfig 활성화

@ConditionalOnMissingBean 패턴

Auto-Configuration의 핵심 패턴이다. “사용자가 직접 정의한 Bean이 없을 때만 기본 Bean을 등록”하는 방식으로, 커스터마이징 포인트를 제공한다.

@Configuration
public class ObjectMapperAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean  // 사용자가 ObjectMapper를 직접 등록하면 이 Bean은 무시
    public ObjectMapper objectMapper() {
        return new ObjectMapper()
            .registerModule(new JavaTimeModule())
            .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    }
}

// 사용자 프로젝트에서 커스텀 ObjectMapper 등록
@Configuration
public class CustomConfig {
    @Bean
    public ObjectMapper objectMapper() {
        // 이 Bean이 우선 → Auto-Configuration의 ObjectMapper는 등록 안 됨
        return new ObjectMapper()
            .setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE);
    }
}

Spring Boot의 거의 모든 Auto-Configuration이 이 패턴을 따른다. HikariCP DataSource, RestTemplate, WebClient 등 기본 Bean이 @ConditionalOnMissingBean으로 보호된다.

커스텀 Starter 만들기

팀 공통 라이브러리를 Spring Boot Starter로 만들면 의존성 추가만으로 자동 설정이 적용된다.

1단계: Auto-Configuration 클래스

// my-audit-spring-boot-starter
@Configuration
@ConditionalOnClass(AuditService.class)
@EnableConfigurationProperties(AuditProperties.class)
public class AuditAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnProperty(name = "audit.enabled", havingValue = "true", matchIfMissing = true)
    public AuditService auditService(AuditProperties properties) {
        return new AuditService(properties.getStorageType(), properties.getRetentionDays());
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnBean(AuditService.class)
    public AuditInterceptor auditInterceptor(AuditService auditService) {
        return new AuditInterceptor(auditService);
    }
}

@ConfigurationProperties(prefix = "audit")
@Getter @Setter
public class AuditProperties {
    private boolean enabled = true;
    private String storageType = "database";
    private int retentionDays = 90;
}

2단계: spring.factories 또는 AutoConfiguration.imports

# Spring Boot 2.x: META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
  com.example.audit.AuditAutoConfiguration

# Spring Boot 3.x: META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.audit.AuditAutoConfiguration

3단계: 사용하는 프로젝트

<!-- pom.xml에 의존성만 추가하면 끝 -->
<dependency>
    <groupId>com.example</groupId>
    <artifactId>my-audit-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>
# application.yml — 필요 시 튜닝
audit:
  storage-type: elasticsearch
  retention-days: 180

@Conditional 조합

여러 조건을 조합해 정교한 분기가 가능하다.

@Configuration
@ConditionalOnClass(RedisConnectionFactory.class)        // Redis 라이브러리 존재
@ConditionalOnProperty(name = "app.cache.type", havingValue = "redis")  // redis 타입
public class RedisCacheConfig {

    @Bean
    @ConditionalOnMissingBean(CacheManager.class)         // 사용자 정의 없을 때만
    @ConditionalOnBean(RedisConnectionFactory.class)      // 커넥션 팩토리 존재 시
    public CacheManager redisCacheManager(RedisConnectionFactory factory) {
        return RedisCacheManager.builder(factory)
            .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(30)))
            .build();
    }
}

// SpEL 복합 조건
@Bean
@ConditionalOnExpression(
    "${app.feature.v2:false} and '${spring.profiles.active}'.contains('prod')"
)
public FeatureV2Service featureV2Service() {
    return new FeatureV2Service();
}

커스텀 @Conditional 구현

기본 제공 어노테이션으로 부족하면 직접 만들 수 있다.

// 특정 OS에서만 활성화
public class OnLinuxCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String os = context.getEnvironment().getProperty("os.name", "").toLowerCase();
        return os.contains("linux");
    }
}

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(OnLinuxCondition.class)
public @interface ConditionalOnLinux {}

// 사용
@Bean
@ConditionalOnLinux
public FileWatcher linuxFileWatcher() {
    return new InotifyFileWatcher();
}

디버깅: Auto-Configuration 리포트

# 어떤 Auto-Configuration이 적용/제외되었는지 확인
java -jar app.jar --debug

# 또는 application.yml
debug: true
logging:
  level:
    org.springframework.boot.autoconfigure: DEBUG

# Actuator로 확인
# GET /actuator/conditions
{
  "positiveMatches": {
    "DataSourceAutoConfiguration": [{
      "condition": "OnClassCondition",
      "message": "@ConditionalOnClass found required classes"
    }]
  },
  "negativeMatches": {
    "MongoAutoConfiguration": {
      "notMatched": [{
        "condition": "OnClassCondition",
        "message": "@ConditionalOnClass did not find required class"
      }]
    }
  }
}

/actuator/conditions 엔드포인트는 모든 @Conditional 평가 결과를 보여준다. Auto-Configuration이 왜 적용됐는지, 왜 안 됐는지 한눈에 확인할 수 있다. Actuator 운영 가이드도 참고하자.

Auto-Configuration 순서 제어

@AutoConfiguration(
    after = DataSourceAutoConfiguration.class,
    before = FlywayAutoConfiguration.class
)
@ConditionalOnBean(DataSource.class)
public class MyDbMigrationAutoConfiguration {
    // DataSource 설정 후, Flyway 전에 실행
}

정리

패턴 용도
@ConditionalOnProperty Feature Flag, 환경별 분기
@ConditionalOnMissingBean 기본값 제공 + 커스터마이징 허용
@ConditionalOnClass 선택적 의존성 지원
커스텀 Starter 팀 공통 설정 배포

@Conditional을 이해하면 Spring Boot의 자동 설정 마법이 투명해진다. 커스텀 Starter를 만들어 팀 공통 설정을 의존성 하나로 배포하고, @ConditionalOnMissingBean으로 확장 포인트를 열어두자.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux