万字长文剖析 Spring Boot 4 自动配置机制源码:从 @EnableAutoConfiguration 到条件装配

本文是 Spring Boot 4 系列第 11 篇 | 基于 Spring Boot 4.1.0 GA 源码 + Spring Framework 7.0.x + JDK 17 基线 | 预计阅读 35 分钟

文末附「全链路总结图」和「三大核心设计思想」,写自定义 starter 或排查自动配置失效时可直接对照。


写在前面

pom.xml 里加一个 spring-boot-starter-web,一行配置不写,应用启动后自动有了 DispatcherServletRequestMappingHandlerAdapter、内嵌 Tomcat、JSON 序列化器。往容器里手动注册一个自己的 ObjectMapper,Jackson 自动配置的 @ConditionalOnMissingBean 会判定你已定义,直接退让。

这两件事背后是同一套机制:自动配置(Auto-configuration)。

这篇基于 Spring Boot 4.1.0 GA 源码,从 @EnableAutoConfiguration 注解出发,把自动配置从「注解入口 → 候选加载 → 两道过滤 → 拓扑排序 → Bean 注册」的完整链路走一遍,重点回答五个问题:

  1. 候选类列表是从哪里加载的?为什么 Spring Boot 4 不再用 spring.factories?
  2. 上百个自动配置类,JVM 是怎么做到不加载类字节码就快速剔除掉大部分候选的?
  3. @ConditionalOnClass / @ConditionalOnBean / @ConditionalOnWebApplication 的底层判定逻辑到底是什么?
  4. 自动配置类的执行顺序是怎么排出来的?
  5. AOT 模式下,这些条件评估为什么被搬到了构建期?

内容速览

  • 自动配置全景图:从 @EnableAutoConfiguration 到 Bean 注册的完整链路
  • 入口机制:DeferredImportSelector 为什么是"退让"的根基
  • 核心管线:getAutoConfigurationEntry() 的 8 步流程
  • 候选加载:从 spring.factories 到 AutoConfiguration.imports,ImportCandidates 的实现
  • 4.x 模块化自动配置:128 个模块、99 个 imports 文件、AutoConfigurationReplacements 迁移垫片
  • 第一道过滤:编译期元数据 + Class.forName,不加载字节码剔除候选
  • 第二道评估:ConditionEvaluator 两阶段,OnClassCondition / OnBeanCondition / OnWebApplicationCondition 的判定细节
  • 排序:字母序 → @AutoConfigureOrder → 拓扑排序 + 环检测
  • 排错利器:ConditionEvaluationReport 与 --debug 报告、Actuator conditions 端点
  • AOT 架构:基础设施下沉 Spring Framework 7,条件评估搬到构建期
  • 实战:手写一个 starter,断点跟踪条件评估全过程

一、自动配置全景图

先看一张总览图(建议右键新标签页打开):

less 复制代码
@SpringBootApplication (组合注解)
  └─ @EnableAutoConfiguration
        └─ @Import(AutoConfigurationImportSelector.class)   ← 入口:延迟导入选择器
              │
              └─ ConfigurationClassParser(Spring Framework)按 DeferredImportSelector 语义,
                 在「所有用户 @Configuration 处理完之后」才调用:
                    └─ AutoConfigurationImportSelector.selectImports()
                          └─ getAutoConfigurationEntry()     ← 核心管线
                                ├─ [1] getAttributes()                        读取 exclude/excludeName 属性
                                ├─ [2] getCandidateConfigurations()           ImportCandidates 扫描
                                │        └─ META-INF/spring/<注解全名>.imports   ← 每个 jar 一个文件,合并
                                ├─ [3] removeDuplicates()                      去重
                                ├─ [4] getExclusions()                         合并注解排除 + spring.autoconfigure.exclude 属性
                                ├─ [5] checkExcludedClasses()                  校验排除项合法性
                                ├─ [6] configurations.removeAll(exclusions)    应用排除
                                ├─ [7] getConfigurationClassFilter().filter()  ★ 第一道过滤(不加载字节码)
                                │        ├─ OnClassCondition       ← 按 classpath 类存在性过滤
                                │        ├─ OnBeanCondition        ← 按条件涉及的类存在性过滤(Bean 判定留到注册阶段)
                                │        └─ OnWebApplicationCondition
                                └─ [8] fireAutoConfigurationImportEvents()     发布导入事件
                                      └─ AutoConfigurationGroup.selectImports()
                                            └─ AutoConfigurationSorter.getInPriorityOrder()  ★ 排序
                                                  ├─ ① 字母序
                                                  ├─ ② @AutoConfigureOrder
                                                  └─ ③ @AutoConfigureBefore / @AutoConfigureAfter 拓扑排序
      │
      └─ 过滤后的自动配置类以 @Configuration 身份进入标准 Bean 加载流程
            └─ ConditionEvaluator.shouldSkip()   ★ 第二道(完整)条件评估
                  ├─ PARSE_CONFIGURATION 阶段: @ConditionalOnClass / OnWebApplicationCondition 等
                  └─ REGISTER_BEAN 阶段:       @ConditionalOnBean / @ConditionalOnMissingBean
                        └─ SpringBootCondition.matches() 模板方法
                              ├─ getMatchOutcome()          ← 子类实现判定逻辑
                              ├─ logOutcome()               ← 输出 "Condition ... matched/did not match"
                              └─ recordEvaluation()         ← 写入 ConditionEvaluationReport(--debug 报告)

整个机制可以总结成一句话:"扫描候选 → 不加载类快速过滤 → 完整条件评估 → 拓扑排序 → 注册为普通 @Configuration"

下面逐层拆开。


二、入口:@EnableAutoConfiguration 与 DeferredImportSelector

2.1 注解本体

@SpringBootApplication 是一个组合注解,其中就组合了 @EnableAutoConfiguration。在 4.1.0 中它的定义位于:

core/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/EnableAutoConfiguration.java

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
    // exclude() 按类排除;excludeName() 按类名排除(String,可加载不存在的类)
}

两个关键点:

  • @AutoConfigurationPackage :把主类所在包注册为"自动配置包",供 AutoConfigurationPackages 查询(@Entity 扫描、MyBatis mapper 扫描等都会用到它)。底层由 AutoConfigurationPackages.Registrar(core/spring-boot-autoconfigure/.../AutoConfigurationPackages.java:129)注册一个名为 org.springframework.boot.autoconfigure.AutoConfigurationPackages 的基础设施 Bean。
  • @Import(AutoConfigurationImportSelector.class) :把自动配置的"挑选逻辑"交给一个 DeferredImportSelector

2.2 为什么必须是 DeferredImportSelector?

AutoConfigurationImportSelector 的类声明(core/spring-boot-autoconfigure/.../AutoConfigurationImportSelector.java:78):

java 复制代码
public class AutoConfigurationImportSelector implements DeferredImportSelector,
        BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

DeferredImportSelector 是 Spring Framework 7 中 ImportSelector 的一个子接口,它的语义是:延迟到所有普通 @Configuration 类都处理完之后,再处理这批导入

这解释了为什么用户手动定义的 Bean 总能赢过自动配置:用户自己的配置类在 ConfigurationClassParser 的第一轮就解析完了,而自动配置的导入被推迟到最后一轮。先注册的 Bean 定义,让 @ConditionalOnMissingBean 有机会"看到"它们并主动退让。这正对应 @EnableAutoConfiguration javadoc 里那句话:

Auto-configuration is always applied after user-defined beans have been registered.

延迟导入发生在任何条件判断之前,是自动配置能"退让"的前提。

2.3 触发时机:Spring Framework 的 ConfigurationClassParser

selectImports() 是在谁的手里被调用的?答案是 Spring Framework 7 的 ConfigurationClassParserrefresh()invokeBeanFactoryPostProcessors()ConfigurationClassPostProcessorConfigurationClassParser.parse(),流程大致是:

  1. 解析所有候选配置类(主类、@Component 扫描结果、@Import 等);
  2. 遇到 DeferredImportSelector,不立即处理 ,先放进 deferredImportSelectorHandler 队列;
  3. 所有普通配置类解析完毕后,再调用 deferredImportSelectorHandler.process();
  4. process() 里按 getImportGroup() 分组,每个 Group 先 process()(收集数据)、再 selectImports()(产出结果)------这就是 DeferredImportSelector.Group 接口的语义。

AutoConfigurationImportSelector.getImportGroup() 返回 AutoConfigurationGroup.class(AutoConfigurationImportSelector.java:158),这是 Boot 的一个内部类,后面会看到它的作用。


三、核心管线:getAutoConfigurationEntry()

当 Group 被驱动时,AutoConfigurationImportSelector.selectImports() 调用了核心方法 getAutoConfigurationEntry()(AutoConfigurationImportSelector.java:142):

java 复制代码
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

8 步管线,职责清晰:

步骤 方法 职责
1 isEnabled() 检查环境属性 spring.boot.enableautoconfiguration(EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY)是否为 false,默认启用(AutoConfigurationImportSelector.java:162)
2 getAttributes() 读取注解上的 exclude / excludeName 属性
3 getCandidateConfigurations() 加载所有候选自动配置类(下一节详解)
4 removeDuplicates() 去重(多 jar 声明同名类时只保留一个)
5 getExclusions() 合并「注解排除」+「spring.autoconfigure.exclude 属性排除」
6 checkExcludedClasses() 校验排除项是否真实存在,防止写错类名
7 getConfigurationClassFilter().filter() 第一道过滤:不加载类字节码,仅凭元数据快速剔除
8 fireAutoConfigurationImportEvents() 发布 AutoConfigurationImportEvent,通知监听器

注意第 8 步------过滤之后才发事件。ConditionEvaluationReportAutoConfigurationImportListener(通过 spring.factories 注册为 AutoConfigurationImportListener)会在这里把候选类和排除项记入 ConditionEvaluationReport,这是 --debug 报告的数据来源之一,后面再展开。


四、候选类从哪来:从 spring.factories 到 AutoConfiguration.imports

4.1 历史演进

版本 机制 说明
Boot 1.x ~ 2.6 META-INF/spring.factories org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 为 key 声明自动配置类列表
Boot 2.7 引入 AutoConfiguration.imports 新机制与 spring.factories 双轨并行,@AutoConfiguration 注解登场(2.7 起)
Boot 3.0 全面切换 自动配置读取只走 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,spring.factories 中对应条目被移除
Boot 4.x 彻底告别 spring.factories 中已无任何 EnableAutoConfiguration 条目(本仓库全量 grep 为 0),连"兼容读取"都不存在了

为什么换掉 spring.factories?三个原因:

  1. 性能 :spring.factories 是 properties 格式,SpringFactoriesLoader 需要把整个文件解析成 Map;而 .imports 每行一个类名,加载更快。
  2. 可读性 :.imports 文件就是一个纯列表,Git diff 更友好,review 更容易。
  3. 职责分离 :spring.factories 承担了大量 SPI 注册职责(EnvironmentPostProcessorApplicationListenerFailureAnalyzer 等),把"自动配置列表"从中剥离,两种机制的演进互不拖累。

一个容易搞错的点:Spring Boot 4 并没有废除 spring.factories 机制本身 。本仓库中仍有 70+ 个 spring.factories 文件在正常工作------只是"自动配置类列表"这一项职责被移走了。比如 core/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories 里仍然注册着 ApplicationContextInitializerApplicationListenerAutoConfigurationImportFilterAutoConfigurationImportListenerFailureAnalyzer 等 SPI。所以准确的说法是:自动配置的声明方式变了,SPI 机制没变

4.2 4.1.0 中的加载实现:ImportCandidates

读取 .imports 文件的不是 Boot 的 SpringFactoriesLoader,而是 ImportCandidates(位于 core/spring-boot/src/main/java/org/springframework/boot/context/annotation/ImportCandidates.java)。

AutoConfigurationImportSelector.getCandidateConfigurations()(AutoConfigurationImportSelector.java:200):

java 复制代码
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 注意:默认读的是 AutoConfiguration.class 的全名,不是 EnableAutoConfiguration!
    ImportCandidates importCandidates = ImportCandidates.load(this.autoConfigurationAnnotation, getBeanClassLoader());
    List<String> configurations = importCandidates.getCandidates();
    Assert.state(!CollectionUtils.isEmpty(configurations),
            "No auto configuration classes found in " + "META-INF/spring/"
                    + this.autoConfigurationAnnotation.getName() + ".imports. If you "
                    + "are using a custom packaging, make sure that file is correct.");
    return configurations;
}

ImportCandidates.load() 的机制(ImportCandidates.java:81):

java 复制代码
public static ImportCandidates load(Class<?> annotation, @Nullable ClassLoader classLoader) {
    ...
    String location = String.format(LOCATION, annotation.getName());   // LOCATION = "META-INF/spring/%s.imports"
    Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);   // classLoader.getResources(location)
    List<String> importCandidates = new ArrayList<>();
    while (urls.hasMoreElements()) {
        URL url = urls.nextElement();
        importCandidates.addAll(readCandidateConfigurations(url));     // 逐行读取、去注释、trim
    }
    return new ImportCandidates(importCandidates);
}

两个细节值得注意:

  • classLoader.getResources(location) :按资源路径扫描整个 classpath,每个 jar 里的同名文件都会被读到并合并 。这正是模块化自动配置的基础------每个模块自带一份 .imports 文件,谁在 classpath 上,谁的自动配置就生效。
  • 文件格式 :每行一个类全名,# 开头是注释。比如 core/spring-boot-autoconfigure 自己的 imports 文件前几行:
properties 复制代码
# core/spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.availability.ApplicationAvailabilityAutoConfiguration
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
...

4.3 4.x 的模块化自动配置:核心瘦身 + 模块自治

这是 Spring Boot 4 在自动配置领域最重要的结构性变化。对比一下:

Spring Boot 3.x :几乎所有自动配置都堆在 spring-boot-autoconfigure 一个模块里,一个 imports 文件一百多行,模块重达几 MB,哪怕只用 JSON 序列化,也要引入整个 autoconfigure 模块。

Spring Boot 4.x :仓库重构为 core/(8 个核心模块) + module/(128 个模块 )两层结构,自动配置被拆散到各个功能模块中,每个模块在自己的 jar 里声明自己的 imports 文件:

bash 复制代码
core/spring-boot-autoconfigure/src/main/resources/META-INF/spring/...AutoConfiguration.imports   ← 仅 12 行(核心基建)
module/spring-boot-jackson/src/main/resources/META-INF/spring/...AutoConfiguration.imports     ← 1 行:JacksonAutoConfiguration
module/spring-boot-gson/src/main/resources/META-INF/spring/...AutoConfiguration.imports         ← 1 行:GsonAutoConfiguration
module/spring-boot-webmvc/src/main/resources/META-INF/spring/...AutoConfiguration.imports       ← 6 行:DispatcherServlet 等
...

整个仓库共有 99 个 AutoConfiguration.imports 文件,共同构成全部候选。同时包名也改为「按特性分组」的新体系:org.springframework.boot.jackson.autoconfigure.JacksonAutoConfigurationorg.springframework.boot.webmvc.autoconfigure.WebMvcAutoConfiguration......(老的 org.springframework.boot.autoconfigure.* 包名下只保留 admin / aop / availability / cache / condition / context / data / jmx / ssl / task 等核心子包,webmvcjackson 等大功能全部搬走;另有 spring-boot-autoconfigure-classic 等兼容模块供迁移期使用。)

加载逻辑没有变化。AutoConfigurationImportSelector 是通用的------它不看模块、不分组,只是通过 ImportCandidates 把 classpath 上所有 jar 的 imports 文件全部合并。模块化对 selector 完全透明:拆分只发生在声明层面,运行机制零改动

4.4 4.x 起引入:AutoConfigurationReplacements

spring-boot-autoconfigure 模块根包里还有一个独立类 AutoConfigurationReplacements(4.0 开发期引入,提交信息为 "Provide support for deprecated auto-configuration classes"):

java 复制代码
// AutoConfigurationReplacements.java
final class AutoConfigurationReplacements {
    private static final String LOCATION = "META-INF/spring/%s.replacements";
    ...
}

它的作用(javadoc 原话):处理那些已被废弃或移动的自动配置类 ------比如老配置里 @AutoConfigureBefore / @AutoConfigureAfter / 排除项仍引用旧类名,通过 .replacements 文件把旧类名映射到新类名。AutoConfigurationImportSelector 中对应字段在 L107,懒加载逻辑在 L296-300;getExclusions()(L247)末尾会执行 getAutoConfigurationReplacements().replaceAll(excluded),排序阶段(AutoConfigurationSorter)也会套用同样的替换。这是 4.x 模块化重构后,为了保证老配置(spring.autoconfigure.exclude、老 imports)不因类名搬迁而失效而设计的"平滑迁移垫片"。


五、第一道过滤:不加载字节码,先剔除大部分候选

5.1 为什么需要"提前过滤"?

候选自动配置类可能有上百个,如果全部交给 Spring 的配置类解析器(它会用 ASM 读每个类的字节码、解析注解),开销可观。Boot 的设计是:在真正读取类字节码之前,先用最廉价的手段(类存在性检查 + 编译期生成的元数据)把明显不匹配的候选剔除

这就是 AutoConfigurationImportFilter 接口的用途(core/spring-boot-autoconfigure/.../AutoConfigurationImportFilter.java:59):

java 复制代码
@FunctionalInterface
public interface AutoConfigurationImportFilter {
    boolean[] match(@Nullable String[] autoConfigurationClasses,
            AutoConfigurationMetadata autoConfigurationMetadata);
}
  • 入参:全部候选类名数组 + AutoConfigurationMetadata(编译期生成的元数据);
  • 返回值:与入参等长的 boolean[],false 的候选直接被丢弃;
  • 语义(javadoc 原文):"This interface is designed to allow fast removal of auto-configuration classes before their bytecode is even read"------在读取字节码之前快速移除自动配置类。

5.2 过滤器从哪来?还是 spring.factories

过滤器本身通过 SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, ...) 加载(AutoConfigurationImportSelector.java:279),注册位置在 spring-boot-autoconfigure 的 spring.factories:

properties 复制代码
# core/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

三个过滤器正是三个最常用的条件类,同时承担两个职责:既是 AutoConfigurationImportFilter(提前过滤),又是 Condition(最终评估) ,统一在抽象类 FilteringSpringBootCondition 里:

java 复制代码
// core/spring-boot-autoconfigure/.../condition/FilteringSpringBootCondition.java:42
abstract class FilteringSpringBootCondition extends SpringBootCondition
        implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware {

    @Override
    public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
        ConditionOutcome[] outcomes = getOutcomes(autoConfigurationClasses, autoConfigurationMetadata);
        boolean[] match = new boolean[outcomes.length];
        for (int i = 0; i < outcomes.length; i++) {
            match[i] = (outcomes[i] == null || outcomes[i].isMatch());
            if (!match[i]) {
                // 不匹配的写入 ConditionEvaluationReport
                ...
            }
        }
        return match;
    }
    ...
}

注意 match[i] = (outcomes[i] == null || outcomes[i].isMatch()):返回 null 表示"现在判断不了,放行,后面再仔细判"

5.3 元数据从哪来:编译期生成的 spring-autoconfigure-metadata.properties

AutoConfigurationMetadata 的默认实现来自 AutoConfigurationMetadataLoader(core/spring-boot-autoconfigure/.../AutoConfigurationMetadataLoader.java:38):

java 复制代码
private static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";

这个文件不是手写的 ,而是编译期由注解处理器生成的。处理器位于 core/spring-boot-autoconfigure-processor/,名叫 AutoConfigureAnnotationProcessor,它的 @SupportedAnnotationTypes 明确声明了处理 8 个注解:

java 复制代码
@SupportedAnnotationTypes({
    "org.springframework.boot.autoconfigure.condition.ConditionalOnClass",
    "org.springframework.boot.autoconfigure.condition.ConditionalOnBean",
    "org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate",
    "org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication",
    "org.springframework.boot.autoconfigure.AutoConfigureBefore",
    "org.springframework.boot.autoconfigure.AutoConfigureAfter",
    "org.springframework.boot.autoconfigure.AutoConfigureOrder",
    "org.springframework.boot.autoconfigure.AutoConfiguration" })

它把每个自动配置类上的条件注解编译期就静态分析出来 ,写入 spring-autoconfigure-metadata.properties,格式大致是:

properties 复制代码
# 生成的格式示意(运行时按 key 读取)
com.example.GreetingAutoConfiguration.ConditionalOnClass=com.example.sdk.GreetingClient
com.example.GreetingAutoConfiguration.AutoConfigureAfter=org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration

有了这份"预计算"的元数据,过滤阶段查一个条件只是 Map 查找 + 类名判存在,完全不碰字节码。

5.4 OnClassCondition 的过滤路径:并行 + 兜底

OnClassCondition 是三个过滤器中 @Order(Ordered.HIGHEST_PRECEDENCE) 的(OnClassCondition.java:45),最先执行。它的 getOutcomes() 做了一个并行优化(OnClassCondition.java:49):

java 复制代码
@Override
protected final @Nullable ConditionOutcome[] getOutcomes(@Nullable String[] autoConfigurationClasses,
        AutoConfigurationMetadata autoConfigurationMetadata) {
    // 候选多于 1 个且机器多于 1 核时,拆成两半,一半放后台线程并行判定
    if (autoConfigurationClasses.length > 1 && Runtime.getRuntime().availableProcessors() > 1) {
        return resolveOutcomesThreaded(autoConfigurationClasses, autoConfigurationMetadata);
    }
    ...
}

resolveOutcomesThreaded() 把数组从中间拆开,前半段交给 ThreadedOutcomesResolver(内部新起一个线程),后半段主线程直接算,再 System.arraycopy 拼回结果。注释说明:只多开一个线程,再多反而变慢。

真正的判定在 StandardOutcomesResolver(OnClassCondition.java:183):

java 复制代码
// 从编译期元数据里取这个自动配置类的 @ConditionalOnClass 候选类名列表
List<String> candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");
...
try {
    outcome = getOutcome(candidates);   // 逐个 Class.forName 判存在性
} catch (Exception ex) {
    // 抛异常时返回 null ------ 注释:'We'll get another chance later'
}

注释 "We'll get another chance later" 是关键:过滤阶段只做类存在性这类廉价判断,如果判定过程抛异常(比如类加载器边界问题),返回 null 放行,交给完整的条件评估阶段再判一次------后面还有一道关。

5.5 ClassNameFilter:比 ClassUtils.forName 更快的存在性检查

FilteringSpringBootCondition 里定义了 ClassNameFilter 枚举(PRESENT / MISSING):

java 复制代码
// FilteringSpringBootCondition.java:115
static Class<?> resolve(String className, ClassLoader classLoader) {
    // 用 Class.forName(className, false, ...):不初始化类!比 ClassUtils.forName 快
    return Class.forName(className, false, classLoader);
}

两个细节:

  • initialize = false:只加载不初始化,避免触发 static 块,更快更安全;
  • ClassNameFilter.isPresent 内部 catch 一切 Throwable 返回 false------不因类加载异常把整个启动打崩。

经过这一轮过滤,大批候选(比如 classpath 上根本没有对应 SDK 的那些)被 false 掉,剩下的才进入真正的条件评估。


六、第二道关:Condition 接口与 SpringBootCondition 模板方法

6.1 条件评估发生在哪两个阶段?

过滤后的自动配置类被作为 @Configuration 类重新进入 Spring Framework 的标准解析流程。此时真正的评估者是 Spring Framework 的 ConditionEvaluator(org.springframework.context.annotation.ConditionEvaluator),它在两个阶段各评估一次:

阶段 时机 评估对象
PARSE_CONFIGURATION 配置类解析时 类级条件(@ConditionalOnClass@ConditionalOnWebApplication 等)
REGISTER_BEAN Bean 定义注册时 方法级条件 + 需要"看已注册 Bean"的条件(@ConditionalOnBean@ConditionalOnMissingBean)

阶段语义由 Spring Framework 的 ConfigurationCondition 接口给出:

java 复制代码
public interface ConfigurationCondition extends Condition {
    ConfigurationPhase getConfigurationPhase();
    enum ConfigurationPhase { PARSE_CONFIGURATION, REGISTER_BEAN }
}

为什么 @ConditionalOnBean 必须在 REGISTER_BEAN 阶段评估? 因为"某个 Bean 是否存在"取决于注册顺序------如果还处于解析阶段,后面 @Bean 方法可能还没注册,判断结果会不准。Boot 里实现 ConfigurationCondition 的有两个:OnBeanCondition(OnBeanCondition.java:88-89,返回 REGISTER_BEAN)和 AbstractNestedCondition(AbstractNestedCondition.java:47,phase 由构造参数指定,是 AnyNestedCondition / AllNestedConditions / NoneNestedConditions 的公共父类);其余条件(OnClassConditionOnWebApplicationCondition 等)都按默认的 PARSE_CONFIGURATION 阶段评估。

6.2 SpringBootCondition:模板方法模式

所有 Boot 条件都继承 SpringBootCondition(core/spring-boot-autoconfigure/.../condition/SpringBootCondition.java:39):

java 复制代码
public abstract class SpringBootCondition implements Condition {

    @Override
    public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        String classOrMethodName = getName(metadata);
        try {
            ConditionOutcome outcome = getMatchOutcome(context, metadata);   // ★ 子类实现
            logOutcome(classOrMethodName, outcome);                          // 输出启动日志
            recordEvaluation(context, classOrMethodName, outcome);           // 写入 ConditionEvaluationReport
            return outcome.isMatch();
        }
        catch (NoClassDefFoundError ex) {
            throw new IllegalStateException(...);
        }
        catch (RuntimeException ex) {
            throw new IllegalStateException(...);
        }
    }

    public abstract ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata);
}
  • matches()final 的,固定了"判定 → 日志 → 记录"三段式流程;
  • 子类只需实现 getMatchOutcome(),返回 ConditionOutcome(是否匹配 + ConditionMessage 说明);
  • NoClassDefFoundError / RuntimeException 会被包装成 IllegalStateException------条件判定失败绝不能静默吞掉。

6.3 OnClassCondition:类存在性判定

java 复制代码
@Order(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends FilteringSpringBootCondition {
    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        ClassLoader classLoader = context.getClassLoader();
        List<String> onClasses = getCandidates(metadata, ConditionalOnClass.class);
        if (onClasses != null) {
            List<String> missing = filter(onClasses, ClassNameFilter.MISSING, classLoader);
            if (!missing.isEmpty()) {
                return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class)
                    .didNotFind("required class", "required classes")
                    .items(Style.QUOTE, missing));   // ← 这就是日志里 "did not find required classes" 的来源
            }
            ...
        }
        // 对 @ConditionalOnMissingClass 同理,但语义反转:存在"不该存在的类" → noMatch
        ...
    }
}

关键细节在 getCandidates()(OnClassCondition.java:117):

java 复制代码
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(annotationType.getName(), true);
// 注意第二个参数 classValuesAsString = true!
// 把 @ConditionalOnClass(value = X.class) 的 Class 字面量统一读成字符串类名

为什么这里能拿到字符串类名? 因为 AnnotatedTypeMetadata 是 Spring Framework 的 MetadataReader(ASM 读取字节码)产生的------注解里的 Class 字面量在 class 文件注解属性中就是以类名的字符串形式存储,ASM 读出它根本不需要加载这个类 。这就是 @ConditionalOnClass javadoc 里那句 "parsed by using ASM before the class is loaded" 的含义(原文:"A {@code Class} value can be safely specified on @Configuration classes as the annotation metadata is parsed by using ASM before the class is loaded" ),也是整个自动配置体系敢写 @ConditionalOnClass(某个不存在的类.class) 而不崩的根基。

另外澄清一个常见误解:OnClassCondition 本身没有任何 ASM 代码 。ASM 读注解发生在 Spring Framework 的 ConfigurationClassParser / MetadataReader 层;Boot 侧只是消费 ASM 解析出来的元数据字符串。本仓库中直接调用 MetadataReader 读类字节码的位置有三处:AbstractNestedCondition.java:149(嵌套成员条件的元注解读取)、AutoConfigurationSorter.java:284(AutoConfigurationClass.getAnnotationMetadata(),见第七节)、NoSuchBeanDefinitionFailureAnalyzer.java:233(启动失败诊断)------其中排序阶段读字节码是"元数据缺失时的兜底",这也正反衬出第一道过滤刻意避免读字节码的性能价值。

6.4 OnBeanCondition:BeanDefinition 层面的判定

java 复制代码
@Order(Ordered.LOWEST_PRECEDENCE)   // L88;三个过滤器里它最后跑
class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {   // L89

    @Override
    public ConfigurationPhase getConfigurationPhase() {
        return ConfigurationPhase.REGISTER_BEAN;   // ★ 必须等 Bean 注册阶段
    }

    @Override
    public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 依次评估:@ConditionalOnBean → @ConditionalOnSingleCandidate → @ConditionalOnMissingBean
        // 任一不匹配,直接短路返回 noMatch
        ...
    }
}

真正干活的 getMatchingBeans()(OnBeanCondition.java:212)在 ConfigurableListableBeanFactory 上按三条路找 Bean:

  1. 按类型 (type):内部通过 getBeanNamesForType(...) 取类型匹配的 Bean 定义(getBeanDefinitionsForType 包装),支持 parameterizedContainer(泛型容器,如 @ConditionalOnBean(parameterizedContainer = List.class) 匹配 List<Foo>);
  2. 按注解 (annotation):扫描 Bean 定义上带指定注解的 Bean;
  3. 按名称 (name)。

并支持 SearchStrategy(CURRENT / ANCESTORS / ALL)、ignoredTypes 忽略名单、scoped proxy 判断等。注意它匹配的是已注册的 BeanDefinition (beanFactory.getBeanNamesForType 不实例化 Bean),所以"手动定义 ObjectMapper → Jackson 自动配置 @ConditionalOnMissingBean 判定存在 → 主动退让"这条链路就发生在这一步。

6.5 OnWebApplicationContext:探针类判定

OnWebApplicationCondition(OnWebApplicationCondition.java:47-48,@Order 在 L47、类声明在 L48,值为 HIGHEST_PRECEDENCE + 20)不直接看容器类型,而是用探针类(probe classes)判断:

java 复制代码
private static final String SERVLET_WEB_APPLICATION_CLASS = "org.springframework.web.context.support.GenericWebApplicationContext";
private static final String REACTIVE_WEB_APPLICATION_CLASS = "org.springframework.web.reactive.HandlerResult";

isServletWebApplication()(OnWebApplicationCondition.java:128)依次检查:探针类是否存在 → 是否有 session scope → Environment 是否是 ConfigurableWebEnvironment → resource loader 是否是 WebApplicationContext;isReactiveWebApplication()(L148)同理。required 标志(L93,metadata.isAnnotated(...))让同一个条件类同时服务 @ConditionalOnWebApplication(需要 Web 环境)和 @ConditionalOnNotWebApplication(需要非 Web 环境)两个注解。

另外 @ConditionalOnWebApplication 并没有 matchIfMissing 属性------它"缺省匹配"的语义来自 type() default Type.ANY:deduceType() 取不到 type 时返回 Type.ANY(Servlet 或 Reactive 任一匹配即可)。

6.6 ConditionMessage:日志是怎么生成的

每个 ConditionOutcome 都携带一个 ConditionMessage。它的 Builder API 就是你在启动日志里看到的那些句子的来源:

dart 复制代码
Condition OnClassCondition on com.example.GreetingAutoConfiguration matched
   - @ConditionalOnClass found required class 'com.example.sdk.GreetingClient' (OnClassCondition)

ConditionMessage.forCondition(ConditionalOnClass.class).found("required class", ...).items(Style.QUOTE, ...) 攒出消息体,多个条件用 "; " 连接,最后随 ConditionOutcomelogOutcome 打印、被 recordEvaluation 写进报告。


七、排序:AutoConfigurationSorter 的三步走

过滤完之后,候选集还会排序 ------顺序很重要,比如 ServletWebServerFactoryAutoConfiguration 必须排在 DispatcherServletAutoConfiguration 之前。排序逻辑在 AutoConfigurationSorter.getInPriorityOrder()(core/spring-boot-autoconfigure/.../AutoConfigurationSorter.java:65,注意 4.x 中这个类是包私有的,3.x 里还是 public):

java 复制代码
List<AutoConfigurationClass> getInPriorityOrder(Collection<String> classNames) {
    List<AutoConfigurationClass> orderedClasses = ...;
    // ① 按类名字母序排序
    ...
    // ② 按 @AutoConfigureOrder 排序(数值小的在前)
    orderedClasses.sort((c1, c2) -> Integer.compare(c1.getOrder(), c2.getOrder()));
    // ③ 按 @AutoConfigureBefore / @AutoConfigureAfter 做拓扑排序
    doSortByAfterAnnotation(orderedClasses);
    checkForCycles(orderedClasses);
    return orderedClasses;
}

三个细节:

  1. 字母序:保证排序的确定性,同权重的类按类名字典序排;
  2. @AutoConfigureOrder :读 order 时优先读编译期元数据 (AutoConfigurationClass.getOrder(),AutoConfigurationSorter.java:243,key 为 "AutoConfigureOrder"),读不到再反射读注解 value,默认 AutoConfigureOrder.DEFAULT_ORDER = 0;
  3. 拓扑排序 :@AutoConfigureBefore / @AutoConfigureAfter 构成有向边,doSortByAfterAnnotation 做拓扑排序;类名解析同样优先走元数据 (getClassNames(),L222,key 为 "AutoConfigureBefore" / "AutoConfigureAfter"),并会套用 AutoConfigurationReplacements 做新旧类名替换;最后 checkForCycles(L114)检测环,存在环直接抛异常:
sql 复制代码
AutoConfigure cycle detected between com.example.A and com.example.B

4.x 的 @AutoConfiguration 注解自身就元注解了 @Configuration(proxyBeanMethods = false)@AutoConfigureBefore@AutoConfigureAfter(AutoConfiguration.java:58-61),并提供了 before() / beforeName() / after() / afterName() 四个别名属性------写自动配置时不用再叠两个注解:

java 复制代码
@AutoConfiguration(before = SomeOtherAutoConfiguration.class)   // 等价于 @AutoConfigureBefore(SomeOtherAutoConfiguration.class)

八、排错利器:ConditionEvaluationReport 与 --debug

条件评估的每一步结果都被记入 ConditionEvaluationReport(core/spring-boot-autoconfigure/.../condition/ConditionEvaluationReport.java):

  • 它是个注册在 BeanFactory 里、名为 "autoConfigurationReport" 的单例 Bean(L55);
  • 内部用 SortedMap<String, ConditionAndOutcomes>(L59,TreeMap 实现)按配置类名记录每个条件的结果;
  • 写入方:SpringBootCondition.recordEvaluation()(SpringBootCondition.java:102)+ FilteringSpringBootCondition.match() 的过滤不匹配记录 + ConditionEvaluationReportAutoConfigurationImportListener(记录候选和排除项,recordExclusionsL95recordEvaluationCandidatesL105)。

谁消费它?ConditionEvaluationReportLoggingListener(通过 spring.factories 注册为 ApplicationContextInitializer)。当启动失败时,它会自动打印完整报告;应用正常启动时,加 --debug 参数就能看到:

markdown 复制代码
============================
CONDITIONS EVALUATION REPORT
============================
Positive matches:
-----------------
   AopAutoConfiguration matched:
      - @ConditionalOnClass found required class
        'org.springframework.aop.config.AopConfigUtils' (OnClassCondition)

Negative matches:
-----------------
   ActiveMQAutoConfiguration:
      Did not match:
         - @ConditionalOnClass did not find required class
           'javax.jms.ConnectionFactory' (OnClassCondition)

Exclusions:
-----------
   org.springframework.boot.autoconfigure.aop.AopAutoConfiguration

(以上为运行 --debug 时的真实报告格式节选,Positive/Negative 示例类参照官方文档 CONDITIONS EVALUATION REPORT 示例,具体类名以你的 classpath 为准。)这份报告是排查"为什么某个自动配置没生效"的第一手依据。生产环境还能通过 Actuator 的 conditions 端点远程查看(注意:4.x 中该端点实现类由 3.x 的 ConditionsEndpoint 更名为 ConditionsReportEndpoint,端点 ID conditions 保持不变)。


九、AOT 模式:把条件评估搬到构建期

9.1 4.x 的重大架构变化:AOT 基础设施进了 Spring Framework

3.x 里这些类都在 Spring Boot 自己的 org.springframework.boot.aot 包里:AotProcessorAotApplicationContextAutoConfigurationAotProcessorGeneratedFactory......在 Spring Boot 4 里它们全部消失了

4.x 把整套 AOT 基础设施下沉到了 Spring Framework 7:

3.x(Spring Boot 自带) 4.x(Spring Framework 7)
org.springframework.boot.aot.AotProcessor org.springframework.context.aot.ContextAotProcessor(spring-context)
AotApplicationContext GenericApplicationContext.refreshForAotProcessing()
AutoConfigurationAotProcessor / ImportAutoConfigurationAotProcessor 已删除,统一走 BeanRegistrationsAotProcessor(spring-beans)
GeneratedFactory / GeneratedBean / CodeGenerator org.springframework.aot.generate.GeneratedClass / GeneratedMethods(spring-core)
@AotGenerated 注解(org.springframework.aot.generated.annotation) 消失;生成类统一由 GeneratedClass 自动加注 org.springframework.aot.generate.Generated

Spring Boot 侧只剩下一个薄入口 SpringApplicationAotProcessor(core/spring-boot/src/main/java/org/springframework/boot/SpringApplicationAotProcessor.java:43),直接继承 Framework 的 ContextAotProcessor

9.2 构建期如何执行一遍启动流程

AOT 处理在构建期执行(Gradle 的 bootProcessAot 任务------任务类型 ProcessAot,以及 Maven 的 process-aot 目标,两者的处理器类名都指向 org.springframework.boot.SpringApplicationAotProcessor)。它的做法是把应用的 main 方法真正跑起来,在特定时机中断:

java 复制代码
// SpringApplicationAotProcessor.java:103 ------ AotProcessorHook
// 注册一个 SpringApplicationRunListener,
// 在 contextLoaded 回调时抛出 AbandonedRunException(context) 提前终止 main,
// 从而捕获一个已构建完成的 GenericApplicationContext

跑完 main 拿到上下文后,关键一步是 GenericApplicationContext.refreshForAotProcessing(runtimeHints)(spring-context),它和完整 refresh() 几乎一样走一遍 invokeBeanFactoryPostProcessors()------AutoConfigurationImportSelector 就在这里被触发,所有条件评估、过滤、排序都在构建期完整执行了一遍 ,然后 freezeConfiguration() 冻结 BeanDefinition。这保证了 AOT 生成的代码与运行时真实结果一致。

之后 ApplicationContextInitializationCodeGenerator 生成一个 <MainClass>__ApplicationContextInitializer 类,BeanRegistrationsAotProcessor 遍历 beanFactory.getBeanDefinitionNames() 为每个 Bean 生成注册代码------自动配置贡献的 Bean 和用户 Bean 一视同仁 ,不再有专门的自动配置 AOT 处理器。生成代码的发现机制是 AotServices.factoriesAndBeans() 读取各模块的 META-INF/spring/aot.factories + 容器内 Bean。spring-boot-autoconfigure 自己的 aot.factories 里注册了:

properties 复制代码
# core/spring-boot-autoconfigure/src/main/resources/META-INF/spring/aot.factories
org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingProcessor
org.springframework.beans.factory.aot.BeanRegistrationExcludeFilter=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer
org.springframework.aot.hint.RuntimeHintsRegistrar=\
org.springframework.boot.autoconfigure.template.TemplateRuntimeHints

注意 ConditionEvaluationReportLoggingProcessor------构建期也会生成条件评估报告,如果某个自动配置在 AOT 构建期被条件拒绝,你能在构建日志里直接看到原因。

9.3 运行期如何切换到生成代码

打包后的应用运行期由 SpringApplication 判断(core/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java):

java 复制代码
// L412:AOT 模式下跳过源码加载
if (!AotDetector.useGeneratedArtifacts()) { ... }

// L421 addAotGeneratedInitializerIfNecessary(...):
// AOT 模式下按 <mainClass>__ApplicationContextInitializer 类名加载生成的 Initializer,
// 用 AotApplicationContextInitializer.forInitializerClasses(...) 包装后置顶执行

找不到生成类时抛 AotInitializerNotFoundException(并有配套的 AotInitializerNotFoundFailureAnalyzer 给出友好诊断)。

带来的变化是:启动期不再需要扫描 classpath、不再需要动态评估条件------启动链路变成"执行生成代码直接注册 Bean",这就是原生镜像 / AOT 应用启动毫秒级的根本原因之一。


十、实战:手写一个自定义自动配置,并跟踪条件评估全过程

理论拆完,动手写一个自动配置,用断点走完全链路。

10.1 场景

假设你的公司有一个内部 SDK com.example:greeting-sdk,提供 GreetingClient(访问远程问候服务)。你想做一个 starter:classpath 上有 GreetingClient 时自动配好 GreetingService,且用户自定义了 GreetingService 时必须退让

10.2 三步:自动配置类 + imports 文件 + 元数据处理器

① 自动配置类 (新写法,用 @AutoConfiguration 而不是 @Configuration):

java 复制代码
// com/example/greeting/autoconfigure/GreetingAutoConfiguration.java
@AutoConfiguration(
    afterName = "org.springframework.boot.jackson.autoconfigure.JacksonAutoConfiguration")   // 晚于 Jackson 配置
@ConditionalOnClass(GreetingClient.class)          // classpath 有 SDK 才生效(元注解中不可用 Class 字面量,这里直接用没问题)
@EnableConfigurationProperties(GreetingProperties.class)
public class GreetingAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean                      // 用户自己定义了 GreetingService 就退让
    public GreetingService greetingService(GreetingClient client) {
        return new GreetingService(client);
    }
}

② 声明候选(放在 starter 模块的 resources 下):

properties 复制代码
# META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
com.example.greeting.autoconfigure.GreetingAutoConfiguration

③ 编译期元数据 :starter 的 build 里引入 spring-boot-autoconfigure-processor 作为 annotation processor(它扫描 @ConditionalOnClass 等注解,生成 spring-autoconfigure-metadata.properties)。这样你的自动配置在过滤阶段就能被元数据加速,而不是等运行时反射。

10.3 断点走查

SpringBootCondition.matches()logOutcome 前一行、AutoConfigurationImportSelector.getAutoConfigurationEntry() 第一行、AutoConfigurationSorter.getInPriorityOrder() 第一行各打一个断点,启动应用,观察调用栈:

scss 复制代码
① getAutoConfigurationEntry()
     ├─ getCandidateConfigurations()  → ImportCandidates.load(...) 扫描到你的 imports 文件
     └─ getConfigurationClassFilter().filter()
          └─ OnClassCondition.getOutcomes()   ← 元数据命中,Class.forName("com.example.sdk.GreetingClient") 存在 → 放行
② ConfigurationClassParser 把 GreetingAutoConfiguration 当作 @Configuration 解析
     └─ ConditionEvaluator.shouldSkip(metadata, PARSE_CONFIGURATION)
          └─ OnClassCondition.getMatchOutcome()   ← 匹配 → 进入候选
③ ConfigurationClassBeanDefinitionReader 注册 @Bean 方法
     └─ ConditionEvaluator.shouldSkip(metadata, REGISTER_BEAN)
          └─ OnBeanCondition.getMatchOutcome()    ← @ConditionalOnMissingBean 检查:
               beanFactory.getBeanNamesForType(GreetingService.class) 为空 → 注册成功

如果你在应用里再写一个 GreetingService@Bean,第三步会看到 getMatchingBeans 命中了你定义的 Bean → 自动配置退让。整个退让机制就是这么实现的。

启动后加 --debug,你会看到自己的自动配置出现在报告里:

kotlin 复制代码
GreetingAutoConfiguration matched:
   - @ConditionalOnClass found required class
     'com.example.sdk.GreetingClient' (OnClassCondition)

10.4 排除与调试

  • 排除自动配置 :@SpringBootApplication(exclude = GreetingAutoConfiguration.class)excludeName = "...",或属性 spring.autoconfigure.exclude=com.example.greeting.autoconfigure.GreetingAutoConfiguration------前两者走注解属性,后者走 getExclusions() 里的 Environment 读取,最终都在第 6 步 removeAll 生效;
  • 查看报告 :--debug / 日志级别 DEBUG 看 CONDITIONS EVALUATION REPORT;生产用 Actuator conditions 端点;
  • AOT 构建排错 :./gradlew bootProcessAot 时关注构建日志里 ConditionEvaluationReportLoggingProcessor 输出的报告------构建期没匹配上的自动配置,运行期同样不会生效,但原因在构建期就能看到。

十一、总结

一张图回顾全文:

scss 复制代码
META-INF/spring/...AutoConfiguration.imports(99 个文件,每行一个类)
        │ ImportCandidates 合并(classLoader.getResources)
        ▼
AutoConfigurationImportSelector.getAutoConfigurationEntry()
        ├─ 去重 / 排除(注解 + spring.autoconfigure.exclude + replacements 替换)
        ├─ ① 过滤:AutoConfigurationImportFilter(编译期元数据 + Class.forName,不加载字节码)
        └─ ② 排序:AutoConfigurationSorter(字母序 → @AutoConfigureOrder → 拓扑排序 + 环检测)
        ▼
作为 @Configuration 进入标准解析
        └─ ③ 条件评估:ConditionEvaluator.shouldSkip()
              ├─ PARSE_CONFIGURATION:OnClassCondition / OnWebApplicationCondition(ASM 元数据 + 探针类)
              └─ REGISTER_BEAN:OnBeanCondition(@ConditionalOnBean / @ConditionalOnMissingBean 查 BeanDefinition)
        ▼
结果写入 ConditionEvaluationReport → --debug 报告 / Actuator conditions

三个核心设计思想:

  1. 延迟导入(DeferredImportSelector)------自动配置永远晚于用户配置,这是"退让"机制的根基;
  2. 两阶段评估(提前过滤 + 完整评估)------能用类名存在性解决的事,绝不去读字节码;能用编译期元数据解决的事,绝不放运行时;
  3. 构建期前置(AOT)------4.x 把 AOT 基础设施下沉到 Spring Framework 7,自动配置的条件评估可以在构建期完整执行一遍并固化为代码,启动期免扫描、免动态条件评估。

相关推荐
星栈26 分钟前
被 Rust async 纠正的三个异步认知
前端·后端·rust
夏雪coding31 分钟前
openpyxl 对账实战:金额浮点、前导零丢失、20 位单号科学计数法
人工智能·后端
程序员老赵42 分钟前
Docker 部署 ZLMediaKit:轻松搭建高性能流媒体服务平台
前端·javascript·后端
SamDeepThinking42 分钟前
从REST到gRPC,一个API选型的思考框架
java·后端·程序员
Zane19941 小时前
接口都能写默认实现了,为什么还需要抽象类
java·后端
未秃头的程序猿1 小时前
读Spring AI源码的方法——不是硬啃,是带着问题去翻
java·后端·spring
liuxiaocheng1 小时前
上下文工程:LangChain 怎么组织「喂给模型的东西」
前端·后端
盖伦发发1 小时前
RAG 能跑≠能用:用 EDD 把 Eval 做成基础设施 (附源码)
人工智能·后端·python·功能测试
阿黎梨梨1 小时前
Docker 容器化实战:从零搭建 Web 服务与反向代理
前端·后端·docker
王中阳Go1 小时前
做了 3 年 Spring Boot,老板让我一周搭个 AI Agent,上线当天被安全叫停:Java 团队落地 AI 的三道坎,我替你踩完了
后端·面试