--- title: "Spring @ConditionalOnBean ordering trap — user-defined @Configuration vs autoconfiguration" source_type: error-note status: raw tags: [spring-boot, conditional, autoconfiguration, ordering, tracing, TDD] created: 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` 런타임 조회로 대체: ```java @Bean @ConditionalOnMissingBean(SpanErrorRecorder.class) SpanErrorRecorder micrometerSpanErrorRecorder(ObjectProvider 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` 런타임 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.realSpanErrorRecorderBeanReplacesNoop` → `NoSuchBeanDefinitionException` ## Evidence - `actually-implemented`: ObjectProvider 패턴으로 교체 후 `TracingActivationContextTest` BUILD SUCCESSFUL. - `locally-verified`: `:app-bootstrap:test` 전체 green.