Files
llm-wiki/raw/errors/spring-conditionalonbean-ordering-user-config-vs-autoconfiguration-2026-06-17.md

3.7 KiB

title, source_type, status, tags, created
title source_type status tags created
Spring @ConditionalOnBean ordering trap — user-defined @Configuration vs autoconfiguration error-note raw
spring-boot
conditional
autoconfiguration
ordering
tracing
TDD
2026-06-17

Spring @ConditionalOnBean ordering trap

Parent

raw/project-notes/ca-skeleton-operational-contract

현상 (Symptom)

TracingConfig(@Configuration — user-defined)에 @ConditionalOnBean(Tracer.class) + @ConditionalOnMissingBean(SpanErrorRecorder.class) 빈 메서드를 추가했다. Tracer bean은 @AutoConfigureObservability(tracing=true)로 autoconfiguration에서 공급된다.

테스트에서 MicrometerSpanErrorRecorder(SpanErrorRecorder 구현) 빈을 context.getBean(SpanErrorRecorder.class)로 조회하면:

NoSuchBeanDefinitionException: No qualifying bean of type 'dev.caskeleton.shared.tracing.SpanErrorRecorder' available

Tracer 빈은 존재(context.getBean(Tracer.class) 성공)하나, @ConditionalOnBean(Tracer.class) 조건은 false로 평가됨.

원인 (Root Cause)

Spring Boot 문서 주의사항: @ConditionalOnBean / @ConditionalOnMissingBean은 bean definition ordering에 민감하다.

  • user-defined @Configuration 클래스(예: @Import(TracingConfig.class))는 Spring이 autoconfiguration보다 먼저 처리한다.
  • 조건 평가 시점(bean definition 등록 단계)에 Tracer는 아직 정의되지 않음 → @ConditionalOnBean(Tracer.class) = false.
  • 결과적으로 micrometerSpanErrorRecorder 빈 메서드 자체가 스킵됨.

Spring 공식 문서 인용:

"When using @ConditionalOnBean and @ConditionalOnMissingBean in component scan configurations, the condition evaluation is not predictable because of the order in which beans are created."

해결 (Fix)

@ConditionalOnBean(Tracer.class) 제거 → ObjectProvider<Tracer> 런타임 조회로 대체:

@Bean
@ConditionalOnMissingBean(SpanErrorRecorder.class)
SpanErrorRecorder micrometerSpanErrorRecorder(ObjectProvider<Tracer> tracerProvider) {
    Tracer tracer = tracerProvider.getIfAvailable();
    if (tracer == null) {
        return SpanErrorRecorder.NOOP;
    }
    return new MicrometerSpanErrorRecorder(tracer);
}

ObjectProvider.getIfAvailable()은 bean instantiation 시점(모든 bean definition이 등록된 후)에 호출되므로 Tracer autoconfiguration bean을 정확히 조회한다.

정리 (Lessons)

  1. @ConditionalOnBean@AutoConfiguration에서만 안전하게 autoconfiguration bean을 조건으로 쓸 수 있다.
  2. user-defined @Configuration + @ConditionalOnBean(autoconfig-provided-bean) = 순서 문제로 항상 false.
  3. 해결 패턴 2가지:
    • ObjectProvider<T> 런타임 lazy resolution (이번 선택).
    • user-defined config를 @AutoConfiguration으로 전환 + AutoConfiguration.imports 등록.
  4. @ConditionalOnMissingBean여전히 유효 — 이미 등록된 bean을 체크하는 것이므로 상대적으로 ordering에 덜 민감하다(단, user-defined bean이 autoconfiguration보다 먼저 등록될 것이 보장되어야 함).

재현 환경

  • Spring Boot 3.5.x / Micrometer Tracing 1.5.12
  • TracingConfig (@Configuration, @EnableConfigurationProperties(TracingSettings.class))
  • Test: @SpringBootTest + @AutoConfigureObservability(tracing=true) + @EnableAutoConfiguration(exclude=[data-layer])
  • TDD red: TracingActivationContextTest.realSpanErrorRecorderBeanReplacesNoopNoSuchBeanDefinitionException

Evidence

  • actually-implemented: ObjectProvider 패턴으로 교체 후 TracingActivationContextTest BUILD SUCCESSFUL.
  • locally-verified: :app-bootstrap:test 전체 green.