2121 字
约 7 分钟
15
S06-X08 · Spring Boot 自动配置与条件装配

引入 spring-boot-starter-web,DispatcherServlet、Jackson、Tomcat 自动就位;加上 @EnableWebMvc,同一批自动配置又整体退让。背后是条件求值 + 注册顺序两件事在裁决,不是"约定"。

不清楚这套机制,症状很固定:自己写的 ObjectMapper 不生效、@ConditionalOnMissingBean 判定为真却仍注册两份、排除项写了没反应。


1. 自动配置的加载路径

Spring Boot 3.x 只认一个入口:META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,每行一个全限定类名,没有 key、没有等号。

对比项 AutoConfiguration.imports(2.7+) spring.factories(旧)
路径 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports META-INF/spring.factories
格式 纯类名,一行一个 key=类名,类名
3.0 之后 唯一入口 该 key 已移除
合并方式 多 jar 条目直接拼接 同一 key 逗号合并
常见错误 仍写 spring.factories → 整类不加载 换行续写被解析成非法类名
com.example.starter.RedisAutoConfiguration
com.example.starter.MetricsAutoConfiguration

文件里不要写 # 注释:读取器会把注释行当类名,启动报 ClassNotFoundException。


2. 条件注解判断什么、何时判断

注解 判断对象 求值阶段 典型用法
@ConditionalOnClass 类路径是否有某类 解析配置类(读 ASM,不加载类) 整个自动配置的开关
@ConditionalOnMissingClass 类不存在 同上 提供降级实现
@ConditionalOnBean 是否已注册某 Bean 注册 Bean 定义阶段 依赖其他配置的产物
@ConditionalOnMissingBean 是否尚无该类型 注册 Bean 定义阶段 让用户配置优先
@ConditionalOnProperty Environment 属性值 解析配置类 enabled、mode 开关
@ConditionalOnWebApplication 应用类型 解析配置类 仅 Web 环境生效
@AutoConfiguration
@ConditionalOnClass(RedisTemplate.class)
@ConditionalOnProperty(prefix = "example.redis", name = "enabled", havingValue = "true", matchIfMissing = true)
public class RedisAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory f) {
        RedisTemplate<String, Object> t = new RedisTemplate<>();
        t.setConnectionFactory(f);
        return t;
    }
}

@ConditionalOnClass 能写在类路径没有该类的配置类上,是因为它读字节码注解元数据(ASM)而非反射加载;代价是注解值必须是字面量,${} 不被求值。@ConditionalOnMissingBean 不写类型时按方法返回类型推断,返回 Object 等于"容器里没有任何 Bean"。


3. 求值顺序与 @AutoConfigureAfter

阶段 常量 求值的条件 含义
一 PARSE_CONFIGURATION 类级:OnClass、OnWebApplication、OnProperty 决定配置类是否进入后续处理
二 REGISTER_BEAN Bean 级:OnBean、OnMissingBean、OnSingleCandidate 决定 @Bean 方法是否注册

@ConditionalOnMissingBean 在阶段二执行,看到的是"到目前为止已注册的 Bean 定义",所以结果取决于谁先被处理。两个自动配置类都提供同类型 Bean 时,加载顺序决定谁生效——这就是 @AutoConfigureAfter 存在的理由:

@AutoConfiguration
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
@ConditionalOnBean(DataSource.class)
public class JdbcTemplateAutoConfiguration { /* ... */ }

它只约束自动配置类之间的顺序;用户 @Configuration 与自动配置的先后由"用户先注册、自动配置后注册"保证,不需要注解干预。


4. 用户配置为什么能覆盖自动配置

覆盖不是替换,而是自动配置发现已有 Bean 后不注册。前提两条:自动配置的 @Bean 带 @ConditionalOnMissingBean;用户配置先完成注册。

@Configuration
public class MyJacksonConfig {
    @Bean
    public ObjectMapper objectMapper() {   // 不带条件
        return new ObjectMapper().findAndRegisterModules();
    }
}
用户写法 结果 原因
同类型 @Bean,名字不同 自动配置让位 OnMissingBean 按类型判定
同类型多个 @Bean 仍让位,注入点可能 NoUniqueBeanDefinitionException 需 @Primary 或 @Qualifier
用子类/接口类型声明 类型匹配规则适用 按可赋值性判断
继承自动配置类覆盖方法 不推荐 条件注解不继承,易产生两个定义

最后两行常被忽视:把 ObjectMapper 包成子类,@ConditionalOnMissingBean(ObjectMapper.class) 依然判"已存在"。


5. 排除自动配置的两种方式

方式 生效时机 适用
@SpringBootApplication(exclude = X.class) 编译期写在注解 应用永久不要
spring.autoconfigure.exclude 启动时读 Environment 按环境临时关闭
spring:
  autoconfigure:
    exclude:
      - org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
      - org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration

重复排除是幂等的。要注意连锁失效:排掉 DataSourceAutoConfiguration 后,依赖 DataSource 的自动配置因 @ConditionalOnBean 不满足一起消失,而报错只说"缺少 DataSource Bean"。


6. 自定义 starter 的最小结构

example-redis-spring-boot-autoconfigure/
├── pom.xml
└── src/main/
    ├── java/com/example/starter/RedisAutoConfiguration.java
    ├── java/com/example/starter/RedisProperties.java
    └── resources/META-INF/spring/
        └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
example-redis-spring-boot-starter/
└── pom.xml      # 只依赖 autoconfigure 模块

autoconfigure 模块放条件与代码,starter 只放依赖。AutoConfiguration.imports 若放在 starter 里,autoconfigure 被单独 @Import 时不生效。


7. @ConfigurationProperties 的绑定与校验

特性 说明 常见误用
松散绑定 example.redis.max-idle ↔ maxIdle ↔ MAX_IDLE 以为 @Value 也支持
构造器绑定 单构造器参数自动绑定,3.x 无需 @ConstructorBinding 仍标旧注解
嵌套对象 内部类需访问器或参与构造器绑定 private static 且无 getter
校验 类上 @Validated + jakarta.validation 约束 只写 @Valid,绑定不触发校验
注册 @EnableConfigurationProperties 或 @ConfigurationPropertiesScan 只写注解不注册,Bean 不存在
@ConfigurationProperties(prefix = "example.redis")
@Validated
public record RedisProperties(
        @NotBlank String host,
        @Min(1) @Max(65535) int port,
        @DefaultValue("2000") Duration timeout) { }

record 走构造器绑定,@DefaultValue 在参数上生效。@Validated 必须加在类上;加在 @Bean 方法上时校验发生在方法调用而非绑定阶段,留不住"写错就启动失败"。


8. 调试自动配置

$ java -jar app.jar --debug
============================
CONDITIONS EVALUATION REPORT
============================
Positive matches:
   RedisAutoConfiguration matched:
      - @ConditionalOnClass found required class '...RedisTemplate' (OnClassCondition)
Negative matches:
   DataSourceAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class 'javax.sql.DataSource'
Exclusions:
   SecurityAutoConfiguration
区块 含义 排查价值
Positive matches 条件全满足、已生效 确认目标配置是否加载
Negative matches 有条件不满足 直接读到缺哪个类/属性
Exclusions 被显式排除 区分"排除"与"条件不满足"
Unconditional classes 无条件配置 通常只有少数基础类

运行期按需取快照:

$ curl -s localhost:8080/actuator/conditions | jq '.contexts[].positiveMatches | keys'

需先暴露 conditions endpoint,且返回的是启动时快照,不是实时状态。


9. 排错

现象 根因 处理
自定义 @Bean 不生效 类型不匹配,或用户配置注册顺序靠后 --debug 核对 OnMissingBean 的判定类型
OnMissingBean 判真却出现两个 Bean 求值时对方尚未注册(顺序问题) @AutoConfigureAfter 固定顺序
自动配置类被 @ComponentScan 扫到,条件失效 扫描范围覆盖了 starter 包 收窄 scanBasePackages,starter 包不与业务包重叠
spring.autoconfigure.exclude 无反应 类名错 / 用了旧包路径 从 --debug 的 Exclusions 区块核对
@ConfigurationProperties Bean 不存在 缺 @EnableConfigurationProperties / @ConfigurationPropertiesScan 补注册,注解本身不是 Bean 定义
配置校验不触发 类上只写 @Valid 改 @Validated,确认 validation starter 在类路径
升级 Boot 3 后 starter 全失效 仍写在 spring.factories 迁到 AutoConfiguration.imports

10. 边界

场景 更合适的做法 理由
运行期动态开关能力 @ConditionalOnProperty + 策略 Bean 条件只在启动时求值一次
按租户/请求切换实现 策略模式 + 路由 条件是容器级,不是请求级
自动配置之间互相依赖 显式 @Import 或 @AutoConfigureOrder 条件无法表达循环依赖
大量 OnBean 串联 构造器显式注入 条件链难调试、失败信息模糊
要求用户必须提供某 Bean 自动配置里直接注入,缺失即失败 OnMissingBean 是"可选"语义

条件是"装配时装不装",不是"运行时用不用"。


11. 自测

  1. AutoConfiguration.imports 与 spring.factories 在 3.x 里是什么关系?
  2. @ConditionalOnClass 为什么能写在"类不存在"的配置类上?它读的是什么?
  3. 两个自动配置类都用 @ConditionalOnMissingBean,结果不确定,靠什么固定?
  4. 用户自定义了 ObjectMapper 的子类,@ConditionalOnMissingBean(ObjectMapper.class) 怎么判?
  5. 排除 DataSourceAutoConfiguration 后,为什么别的自动配置也一起失效?
  6. --debug 报告里 Negative matches 与 Exclusions 的区别?
S06-X08 · Spring Boot 自动配置与条件装配
https://www.clxhxhhr.top/posts/5844/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。