
title: Spring Boot 自动装配不是 Configuration 的替代品Conditional 家族里 3 个容易误判的条件date: 2026-09-25tags: [Spring Boot, 自动装配, Conditional, 源码, Java]2024 年做微服务基础组件升级我们写了一个自定义 starter 给全团队用。starter 里配了一个RestTemplateCustomizer用来统一加 traceId 请求头。本地测试没问题但一上线就发现有的服务生效了有的服务没生效。排查了半天发现是ConditionalOnClass写错了类名有的服务 classpath 里有那个类有的没有。这篇文章把 Spring Boot 自动装配的加载流程和Conditional的判定逻辑拆开聊清楚OnClass、OnMissingBean、OnProperty到底怎么判断以及我踩过的 3 个坑。一、事故现场同一个 starter有的服务生效有的不生效starter 的核心配置类Configuration ConditionalOnClass(name org.springframework.web.client.RestTemplate) public class TraceRestTemplateAutoConfiguration { Bean ConditionalOnMissingBean public RestTemplateCustomizer traceRestTemplateCustomizer() { return restTemplate - restTemplate.getInterceptors().add(new TraceIdInterceptor()); } } class TraceIdInterceptor implements ClientHttpRequestInterceptor { Override public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { request.getHeaders().add(X-Trace-Id, MDC.get(traceId)); return execution.execute(request, body); } }问题出在ConditionalOnClass(name ...RestTemplate)。本地 Spring Boot 服务都有RestTemplate但有一个纯消息消费服务没有 web 依赖classpath 里没有RestTemplate所以这个配置类根本没加载。二、自动装配的加载流程Spring Boot 启动时会读取META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.importsSpring Boot 2.7或META-INF/spring.factories旧版com.example.starter.TraceRestTemplateAutoConfigurationSpringFactoriesLoader会加载这些类然后交给 Spring 容器处理。注意加载的是类名不是 bean 实例。真正决定是否实例化为 bean要看上面的Conditional注解是否满足。AutoConfiguration.imports里的类会被解析为候选配置类然后逐个计算Conditional条件。条件不满足的类会被跳过不会注册任何 bean。三、ConditionalOnClass 的判定逻辑ConditionalOnClass对应的实现是OnClassConditionOrder(Ordered.HIGHEST_PRECEDENCE) class OnClassCondition extends FilteringSpringBootCondition { Override protected ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) { // 使用 ASM 读取配置类上的 ConditionalOnClass 注解 // 检查 classpath 中是否存在指定类 } }ConditionalOnClass可以用两种写法ConditionalOnClass(RestTemplate.class) public class XxxConfiguration { } ConditionalOnClass(name com.example.SomeClass) public class XxxConfiguration { }第一种写法RestTemplate.class会直接引用类。如果 classpath 里没有RestTemplate编译时就会报错。第二种写法用字符串可以在运行时判断避免编译期依赖。对于 starter 这种可能被不同服务引用的场景应该用name ...字符串形式。四、ConditionalOnMissingBean 的坑顺序问题ConditionalOnMissingBean判断容器中是否缺少指定类型的 bean。但它只在当前配置类被解析时判断。如果用户自定义的 bean 在更后面才加载starter 里的 bean 可能已经先创建了。Configuration public class MyAutoConfig { Bean ConditionalOnMissingBean public UserService userService() { return new DefaultUserService(); } }如果用户的配置类UserConfig在MyAutoConfig之后加载那userService()会创建一个DefaultUserService而不是用户自定义的。解决方法是让自动配置类在用户配置之后处理或者明确指定用户 bean 的加载顺序。五、ConditionalOnProperty 的坑默认值和 matchIfMissingConditionalOnProperty(prefix trace, name enabled, havingValue true)这个条件要求trace.enabledtrue才加载。如果没有配置trace.enabled默认是 false配置类不加载。如果想默认开启ConditionalOnProperty(prefix trace, name enabled, havingValue true, matchIfMissing true)matchIfMissing true表示如果没有配置也视为匹配。这个参数很多 starter 都在用但要谨慎否则默认行为可能出乎意料。六、源码条件判断发生在配置类解析阶段ConfigurationClassParser在解析配置类时会调用ConditionEvaluatorclass ConditionEvaluator { public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) { if (metadata null || !metadata.isAnnotated(Conditional.class.getName())) { return false; } ListCondition conditions new ArrayList(); for (String[] conditionClasses : getConditionClasses(metadata)) { for (String conditionClass : conditionClasses) { Condition condition getCondition(conditionClass, this.context.getClassLoader()); conditions.add(condition); } } for (Condition condition : conditions) { ConfigurationPhase requiredPhase null; if (condition instanceof ConfigurationCondition) { requiredPhase ((ConfigurationCondition) condition).getConfigurationPhase(); } if ((requiredPhase null || requiredPhase phase) !condition.matches(this.context, metadata)) { return true; } } return false; } }如果某个Conditional的matches返回 false这个配置类就会被跳过。七、别靠猜--debug 的条件评估报告那次排查花了两小时其中大部分时间在猜哪个条件没过。后来才知道 Spring Boot 自带答案启动加--debug参数或debugtrue配置会打印完整的CONDITIONS EVALUATION REPORTTraceRestTemplateAutoConfiguration: Did not match: - ConditionalOnClass did not find required class org.springframework.web.client.RestTemplate (OnClassCondition) Exclusions: ----------- NoneDid not match区块会逐条列出每个自动配置类没生效的具体条件和原因括号里是对应的 Condition 实现类。如果当时直接看这个报告5 分钟就能定位到是 classpath 缺类而不是盯着代码怀疑逻辑写错了。还有一个源码级的细节值得知道自动配置类之间的顺序靠AutoConfigureBefore/AutoConfigureAfter控制而不是 Spring 的Order。这两个注解只在AutoConfiguration.imports里声明的类之间生效写在普通用户配置类上没有任何效果——我们团队就有同事在业务配置类上加了AutoConfigureAfter然后疑惑为什么不起作用。AutoConfiguration AutoConfigureBefore(DataSourceAutoConfiguration.class) ConditionalOnClass(DataSource.class) public class MyDataSourceDecoratorAutoConfiguration { // 保证在默认数据源装配之前介入 }八、我的取舍判断写 starter 时ConditionalOnClass尽量用字符串形式避免编译期依赖。ConditionalOnMissingBean要考虑自动配置和用户配置的加载顺序。ConditionalOnProperty的默认值要明确慎用matchIfMissing true。不要滥用自动装配。核心业务 bean 最好让用户显式配置不要偷偷自动注入。九、复盘真实数字受影响服务5 个中的 1 个排查时间2 小时修复把ConditionalOnClass(RestTemplate.class)改成name ...验证全团队服务重新发布5 个服务全部生效十、思考题你写过自定义 starter 吗有没有遇到过Conditional条件判断不符合预期的情况