自动配置与 Starter:依赖如何触发装配并安全退让
Starter 是依赖入口,自动配置是条件化的 Spring 配置。应用引入 Starter 后,Maven 把相关 JAR 放进 classpath;Boot 发现自动配置候选,结合类、属性和已经处理的 Bean 定义决定哪些配置生效。用户提供自己的实现时,默认配置可以退让。
从三个模块理解依赖与装配
哪个模块做什么
consumer
└─ greeting-starter 提供一项便捷依赖
└─ greeting-autoconfigure Greeting API、默认配置、imports 资源小型库可以把 API 与自动配置放在一个模块;当业务需要独立使用 API、支持多种装配框架或维护不同兼容线时,再把 API 拆出去。Starter 本身通常没有业务类,更不应在加载依赖时自动注册远程账号、建表或发送消息。它负责聚合必需依赖,副作用由具有明确启动和关闭条件的运行组件执行。
Boot 的 BOM 管理兼容版本,Starter 聚合依赖,两者职责不同。企业内部 Starter 应使用自己的坐标,避免 spring-boot-* 名称让消费者误以为是官方产品。可选 SDK 应使用合理的 optional 依赖和类条件;把所有驱动都传递给应用,会增加体积并触发无关装配。Starter 设计
构建一个默认 Greeting
下载完整多模块工程,在 Linux Bash 解压为 auto-config-starter,进入根目录。使用 Docker、curl 7.76+,实验为 Boot 4.1.1、Java 25、Maven 3.9.12;宿主为有 Docker 权限的普通用户。Boot 系统要求
PROJECT_DIR="$PWD"
CACHE_DIR="$HOME/.cache/boot-08-maven"
BUILD_IMAGE=maven:3.9.12-eclipse-temurin-25
RUN_IMAGE=eclipse-temurin:25.0.4_7-jdk
mkdir -p "$CACHE_DIR"
docker run --rm --user "$(id -u):$(id -g)" -e MAVEN_CONFIG=/tmp/maven \
-v "$PROJECT_DIR:/work" -v "$CACHE_DIR:/cache" -w /work \
"$BUILD_IMAGE" mvn -B -Dmaven.repo.local=/cache clean verifyReactor 依次构建自动配置模块、Starter 和 consumer。四个条件测试通过后,consumer 生成 consumer/target/consumer-1.0.0.jar。构建身份使用宿主 UID/GID,可写项目及缓存;运行身份独立为 10001。首次使用需拉取上述镜像;内网通过受信镜像与 Maven 仓库完成依赖准备,离线加 -o 验证缓存,不能省略测试插件下载。
CONTAINER=boot-starter-lab
docker run -d --name "$CONTAINER" --user 10001:10001 \
--read-only --tmpfs /tmp:rw,nosuid,nodev,size=128m \
-p 127.0.0.1:18082:8080 \
-v "$PROJECT_DIR/consumer/target/consumer-1.0.0.jar:/app.jar:ro" \
"$RUN_IMAGE" java -jar /app.jar
docker logs "$CONTAINER"
curl -q --noproxy '*' --fail-with-body http://127.0.0.1:18082/greeting服务器启动后响应 default。consumer 中没有声明默认 Greeting,也没有扫描 example.greeting 包;自动配置通过 JAR 里的候选资源被发现。
package example.greeting;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
@ConditionalOnClass(Greeting.class)
@ConditionalOnProperty(prefix="demo.greeting", name="enabled", havingValue="true", matchIfMissing=true)
public class GreetingAutoConfiguration {
@Bean
@ConditionalOnMissingBean(Greeting.class)
Greeting greeting() { return () -> "default"; }
}自动配置模块同时提供 public interface Greeting { String message(); }。资源文件的精确位置为:
src/main/resources/META-INF/spring/
org.springframework.boot.autoconfigure.AutoConfiguration.imports
内容:
example.greeting.GreetingAutoConfiguration每行一个自动配置类名。Boot 4.1.1 使用这个 imports 文件发现候选;其他扩展机制仍可能使用 spring.factories,不能把两种资源混为同一用途。自定义自动配置
候选如何成为 Bean 定义
过滤发生在哪个阶段
@EnableAutoConfiguration 通过 AutoConfigurationImportSelector 参与配置类解析。它读取候选,处理 exclusions、去重和过滤,排序后把需要的配置交回 Spring;配置类和方法上的条件继续决定哪些 BeanDefinition 注册。普通业务单例的实例化随后按依赖图进行。候选导入实现
imports 中的类名
├─ 被排除 / class 条件不成立 → 不导入相应配置
└─ 配置候选继续处理
├─ 属性关闭 → 不注册默认能力
└─ 属性允许
├─ 已有 Greeting 定义 → 默认 greeting 方法退让
└─ 没有 Greeting 定义 → 注册默认定义 → 按依赖创建对象条件常见输入包括 classpath、Environment、Web 应用类型、资源以及已处理的 Bean 定义。条件求值期间做网络 I/O,会让启动依赖不稳定的外部响应;调用 getBean() 还可能提前实例化对象,破坏后处理器的正常时序。条件应判断配置是否适用,真正连接外部服务在 Bean 生命周期中处理。
@ConditionalOnClass 放在配置类上,可以通过元数据阻止缺失类对应的配置继续加载。若只放在 @Bean 方法上,而方法签名已经引用缺失类型,JVM 可能先解析签名。可选库配置宜放进独立或静态嵌套配置类,让类条件包住整个依赖类型区域。类条件
@ConditionalOnProperty 的 havingValue="true" 只接受相应值,matchIfMissing=true 表示未设置时启用。演示 Greeting 没有外部副作用,所以默认启用合理;自动付款、远程写入或大规模后台任务应采用更谨慎的显式开关。集合属性不能仅凭某个根键判断整体内容是否存在。属性条件 API
定义顺序与对象依赖
@AutoConfiguration(after=OtherAutoConfiguration.class)、before 和 AutoConfigureOrder 控制自动配置处理顺序。这使后一个配置的 Bean 条件能看到前面已经注册的定义。它们没有把对象 A 的构造动作排到对象 B 前面;构造先后由注入依赖或明确的 dependsOn 等关系决定。AutoConfiguration API
例如观测配置要包装一个已有 Client,自动配置顺序保证条件能够看见 Client 定义,而注入 Client 参数才能表达包装器运行时依赖。仅加 after、却用静态字段访问尚未构造的 Client,仍会失败。配置之间出现顺序环时,应拆出共同基础配置或改成明确的依赖,而不是继续叠 before/after。
自动配置类通过 imports 注册,不应混进 consumer 的组件扫描。否则排除和排序预期可能被扫描路径绕开。示例把 consumer 和 greeting 放在相邻包,主类只扫描 consumer。
用户实现怎样替换默认对象
按公共类型退让
实验 consumer 提供一个只在 user Profile 生效的配置:
@Configuration(proxyBeanMethods=false)
@Profile("user")
class UserConfiguration {
@Bean
Greeting userGreeting() {
return () -> "user";
}
}关闭上个实例,然后沿用启动命令追加 --spring.profiles.active=user。再次访问 /greeting 得到 user。默认方法的名称是 greeting,用户方法的名称是 userGreeting;替换成立依靠 Greeting 类型条件,不依赖同名覆盖。
ConditionalOnMissingBean 只看已经处理的定义,因此适合放在自动配置中;按类型、名称、注解或父上下文搜索会产生不同匹配范围。Boot 4.1.1 还会忽略不是自动装配候选或不是默认候选的 Bean。若返回类型声明成 Object,条件未必能在实例创建前识别真实 Greeting,应公开有意义的返回类型。MissingBean 匹配规则
一个大型 Client 可能由传输、序列化和连接池组成。退让需要确定替换的是 facade 还是整套基础设施;否则用户替换 facade 后,默认连接池仍在后台运行。只改超时或编码器时,可提供 Customizer 回调,减少用户复制整个配置类。多个回调有顺序需求时显式 Ordered,并在最后检查必须保留的 TLS 或认证设置。
用实际 Context 固定正反分支
ApplicationContextRunner 为一次断言创建小型 Context,回调完成后关闭。下面的测试直接检查默认 Bean 消失和用户 Bean 唯一存在:
new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(GreetingAutoConfiguration.class))
.withUserConfiguration(UserConfig.class)
.run(context -> {
assertThat(context).hasSingleBean(Greeting.class);
assertThat(context).doesNotHaveBean("greeting");
assertThat(context.getBean(Greeting.class).message()).isEqualTo("user");
});完整工程另外测试缺省创建、属性关闭和 FilteredClassLoader 隐藏 Greeting 后跳过配置。这些测试检查 BeanFactory 中实际存在的对象和定义。自动配置测试
删除 user 参数,改成 --demo.greeting.enabled=false 启动,接口返回 disabled。Controller 通过 ObjectProvider 处理能力缺失,因此启动仍成功。若业务构造器强制注入 Greeting,则关闭同一能力后应因缺少依赖而失败。开关能否关闭,取决于消费者是否真的支持没有这个对象。
配置属性也是库 API:前缀、类型、单位与默认值变化都会影响消费者。可以引入配置处理器生成 spring-configuration-metadata.json,为 IDE 描述属性及废弃替代项;元数据既不执行校验,也不会自动兼容旧键。Java 25 构建时按处理器文档显式配置 annotationProcessorPaths,避免以为依赖存在就必然运行处理器。配置元数据生成
自动配置没有生效时逐层检查
区分缺候选、条件否决和创建失败
保持默认或 user 实例正在运行,在宿主查看条件报告:
curl -q --noproxy '*' --fail-with-body \
http://127.0.0.1:18082/actuator/conditions搜索 GreetingAutoConfiguration 及其 greeting 方法。默认模式可以看到类条件、属性条件和 MissingBean 的匹配;user 模式的默认方法则因已有 Greeting 不匹配。报告说明装配选择,不检测 Greeting 的业务实现是否正确。生产 conditions 需要鉴权和网络限制,实验仅映射回环端口。
没有候选时,从普通自动配置 JAR 检查资源,而不是先往应用里手工补 Bean。在项目目录执行:
docker run --rm --user "$(id -u):$(id -g)" \
-v "$PROJECT_DIR:/work:ro" -w /work "$RUN_IMAGE" \
jar tf greeting-autoconfigure/target/greeting-autoconfigure-1.0.0.jar列表应包含完整 imports 路径和 GreetingAutoConfiguration.class。源码目录里存在文件,而产物里没有,通常是资源过滤、打包路径或 shading 丢失;修正构建后重新构建,并运行 consumer 发布物。不要把库模块打包为 Boot 可执行 JAR 再供其他应用依赖,其类会位于 BOOT-INF/classes,普通依赖类路径不会按应用启动器方式寻找它。
--debug 也能在启动时输出条件报告;精确排除某项自动配置可用 spring.autoconfigure.exclude,但类名以当前版本为准。随意排除数据源配置可能把“不该引入 JDBC”的依赖问题变成别的缺 Bean 异常。排除自动配置
| 观察到的结果 | 优先定位 |
|---|---|
| 报告中没有候选且 JAR 无 imports | 构建资源路径和发布物 |
| 类条件否决 | 运行依赖被 exclude、optional 未显式引入 |
| 属性条件否决 | 有效属性、拼写和 matchIfMissing |
| MissingBean 否决但对象不符预期 | 用户 Bean 类型、父 Context、返回类型 |
| 条件匹配但启动抛异常 | Bean 实例化的最深 cause、属性绑定和资源权限 |
| IDE 成功、发布 JAR 失败 | 嵌套依赖、资源、构建插件和实际 classpath |
Boot 主版本升级时,重新核对模块坐标、公共扩展 API 与默认配置。条件命中变化应有测试定位原因,而不是在一个 JAR 中用大量反射猜测 Boot 版本。一个兼容线对应清楚的依赖范围,更便于消费者选择和回退。
关闭最后一个演示实例:
docker stop -t 20 "$CONTAINER"
docker rm "$CONTAINER"权威资料与规范地址
条件注解、自动配置注册与 Starter 测试 API 见以下官方资料。
