深析Springboot之自动配置
文章目录
- 1. 自动配置的起点:@SpringBootApplication 与 @EnableAutoConfiguration
- 1.1 @SpringBootApplication:一个复合注解
- 1.2 @EnableAutoConfiguration:开启自动配置的大门
- 2. 核心选择器:AutoConfigurationImportSelector 的职责
- 3. 候选配置的加载:ImportCandidates.load 与 AutoConfiguration.imports 文件 (Spring Boot 3.x)
- 4. 配置类的解析:ConfigurationClassPostProcessor 与 ConfigurationClassParser
- 5. 条件化装配的核心:@Conditional 注解与 ConditionEvaluator
- 6. 条件的具体实现:OnClassCondition 与 OnBeanCondition
- 7. 最终的 Bean 注册与实例化
- 8. 完整执行流程图
- 总结
引言:本篇将从源码层面详细解析 Spring Boot 3.x 的自动配置(Auto-Configuration)核心原理与执行流程。
本文将遵循 Spring Boot 3.x 的实现,系统性地追踪自动配置从启动注解 @SpringBootApplication 开始,直至最终将 Bean 定义注册到 Spring 容器的完整链路。我们将深入剖析其中涉及的关键类、核心方法以及设计思想,包括 @EnableAutoConfiguration 的作用、AutoConfigurationImportSelector 的选择机制、Spring Boot 3.x 中新的 AutoConfiguration.imports 文件加载方式、@Conditional 条件注解的评估过程等。
1. 自动配置的起点:@SpringBootApplication 与 @EnableAutoConfiguration
Spring Boot 的便捷性很大程度上源于其“约定优于配置”的理念,而自动配置是这一理念的核心体现。这一切的魔法始于一个我们非常熟悉的注解:@SpringBootApplication。
1.1 @SpringBootApplication:一个复合注解
从源码上看,@SpringBootApplication 并非一个简单的注解,而是一个精心设计的复合注解。它整合了多个核心注解,为 Spring Boot 应用提供了一站式的启动配置。
// 源码路径: org.springframework.boot.autoconfigure.SpringBootApplication@Target(ElementType.TYPE) // 作用于类、接口或枚举
@Retention(RetentionPolicy.RUNTIME) // 注解信息保留到运行时,可通过反射获取
@Documented // 包含在 Javadoc 中
@Inherited // 子类可以继承该注解
@SpringBootConfiguration // 声明当前类为配置类
@EnableAutoConfiguration // ← 自动配置的关键入口
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {// ...
}
通过分析其元注解,我们可以看到它主要由以下三个核心注解组成 :
- @SpringBootConfiguration: 这个注解本质上是 @Configuration 的一个“特例”。它将标注的类声明为 Spring 的配置类,允许在其中通过 @Bean 定义 Bean。同时,它还为 Spring Boot 的测试框架提供了查找主配置类的能力。
- @ComponentScan: 负责组件扫描。它会自动扫描该注解所在类及其子包下的所有组件(如 @Component, @Service, @Repository 等),并将它们注册到 Spring 容器中。
- @EnableAutoConfiguration: 这是我们本次研究的重中之重。该注解是激活 Spring Boot 自动配置引擎的开关。
1.2 @EnableAutoConfiguration:开启自动配置的大门
@EnableAutoConfiguration 的核心职责是启动 Spring 的自动配置流程。它本身也是一个复合注解,其定义揭示了自动配置的第一个关键步骤。
// 源码路径: org.springframework.boot.autoconfigure.EnableAutoConfiguration@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage // 自动注册主配置类所在的包
@Import(AutoConfigurationImportSelector.class) // ← 导入了自动配置的核心处理器
public @interface EnableAutoConfiguration {// ...
}
这里最关键的一行是 @Import(AutoConfigurationImportSelector.class) 。Spring 框架的 @Import 注解用于导入额外的配置类或 ImportSelector 实现。这里,它将 AutoConfigurationImportSelector 这个类引入到 Spring 容器的初始化流程中。这个类正是整个自动配置机制的“总指挥”。
2. 核心选择器:AutoConfigurationImportSelector 的职责
AutoConfigurationImportSelector 是一个实现了 DeferredImportSelector 接口的类,它是自动配置流程中的核心入口和选择器。当 Spring 容器在处理 @Configuration 注解时,会调用 ImportSelector 实现类的 selectImports 方法,来决定需要额外导入哪些配置类。
完整包路径: org.springframework.boot.autoconfigure.AutoConfigurationImportSelector
它的核心逻辑位于 selectImports 方法中。这个方法的主要任务是:收集所有可能的自动配置类,经过筛选和排序,最后返回一个需要被 Spring 容器加载的配置类列表。
selectImports 方法的执行流程可以概括为以下几步:
- 检查自动配置是否启用: 通过 spring.boot.enableautoconfiguration 属性判断是否需要执行自动配置。
- 获取候选配置: 调用 getCandidateConfigurations 方法获取所有候选的自动配置类名。这是最关键的一步,我们将在下一节详述。
- 移除排除项: 根据 @EnableAutoConfiguration 注解的 exclude 和 excludeName 属性,以及 spring.autoconfigure.exclude 配置,从候选列表中移除用户明确指定不需要的配置类。
- 应用条件过滤: 虽然 ImportSelector 阶段会进行初步过滤,但更精细的条件判断(@Conditional 系列注解)会延迟到配置类实际被解析时进行。
- 触发事件: 发布 AutoConfigurationImportEvent 事件,方便监听器获取导入信息。
- 返回结果: 返回一个包含最终确定的自动配置类全限定名的字符串数组,这些类将作为 @Configuration 类被 Spring 容器处理。
3. 候选配置的加载:ImportCandidates.load 与 AutoConfiguration.imports 文件 (Spring Boot 3.x)
在 AutoConfigurationImportSelector 的 getCandidateConfigurations 方法内部,Spring Boot 需要知道“候选的自动配置类”有哪些。这个列表从哪里来呢?
在 Spring Boot 2.x 时代,这个列表主要通过 SpringFactoriesLoader 工具类读取所有 JAR 包中 META-INF/spring.factories 文件来获取。文件中 org.springframework.boot.autoconfigure.EnableAutoConfiguration 这个键对应的值,就是所有候选的自动配置类。
进入 Spring Boot 3.x 时代,引入了一种更高效、更专一的新机制。 虽然为了兼容性仍保留了对 spring.factories 的支持,但首选的加载方式已经变成了读取 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件 。
这个加载过程由 ImportCandidates 类负责。
完整包路径: org.springframework.boot.autoconfigure.ImportCandidates
getCandidateConfigurations 方法会调用 ImportCandidates.load 静态方法来加载这些候选配置。
ImportCandidates.load 方法实现要点
load 方法的逻辑非常清晰,其目的是在整个应用的 Classpath 中寻找所有符合特定路径的 .imports 文件,并读取其中的内容。
// ImportCandidates.load 方法的伪代码public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {// 1. 确定要查找的文件路径// 它会基于传入的注解类名来构建路径// 对于自动配置,传入的是 AutoConfiguration.class,所以路径是:// "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports"String location = String.format("META-INF/spring/%s.imports", annotation.getName()); // 2. 使用 ClassLoader 在所有 JAR 包和类路径下查找该文件Enumeration<URL> urls = classLoader.getResources(location);List<String> candidates = new ArrayList<>();// 3. 遍历找到的所有 .imports 文件while (urls.hasMoreElements()) {URL url = urls.nextElement();// 4. 读取文件内容,每一行就是一个自动配置类的全限定名// 文件中的 # 开头的内容是注释,会被忽略List<String> lines = readAndParse(url);candidates.addAll(lines);}// 5. 返回包含所有候选类名的 ImportCandidates 对象return new ImportCandidates(candidates);
}
spring-boot-autoconfigure.jar 中的 AutoConfiguration.imports 文件内容示例如下:
# Auto-Configuration Imports
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
...
通过这种方式,Spring Boot 高效地收集到了所有需要考虑进行自动配置的类。
4. 配置类的解析:ConfigurationClassPostProcessor 与 ConfigurationClassParser
AutoConfigurationImportSelector 返回的配置类列表并不会立即被实例化。它们会作为普通的 @Configuration 类,交由 Spring 容器的 ConfigurationClassPostProcessor 进行后续处理。
ConfigurationClassPostProcessor 是一个 BeanFactoryPostProcessor,它在 Spring 容器生命周期的早期阶段被调用,专门用于解析配置类。
完整包路径: org.springframework.context.annotation.ConfigurationClassPostProcessor
它的核心职责是扫描 Bean 定义,找出被 @Configuration 注解标记的类,然后委托给 ConfigurationClassParser 进行深度解析。
完整包路径: org.springframework.context.annotation.ConfigurationClassParser
ConfigurationClassParser 会递归地处理一个配置类及其内部的所有注解,例如 :
- @PropertySource: 加载属性文件。
- @ComponentScan: 扫描组件。
- @Import: 导入其他配置类或 ImportSelector。
- @Bean: 发现 Bean 定义方法。
最重要的是,它在解析每个配置类或 @Bean 方法之前,会进行一次条件检查。这就是 @Conditional 注解发挥作用的地方。
5. 条件化装配的核心:@Conditional 注解与 ConditionEvaluator
Spring Boot 自动配置的精髓在于“按需加载”。例如,只有当 Classpath 中存在 DataSource.class 时,DataSourceAutoConfiguration 才会生效。这种“按需”的能力,就是通过 @Conditional 系列注解实现的。
所有的条件注解(如 @ConditionalOnClass, @ConditionalOnBean)都派生自 @Conditional 元注解。当 ConfigurationClassParser 在解析配置类时,它会使用 ConditionEvaluator 来判断该类上的 @Conditional 注解是否满足条件。
完整包路径: org.springframework.context.annotation.ConditionEvaluator
ConditionEvaluator 的核心方法是 shouldSkip() 。
shouldSkip() 方法执行流程
此方法决定了是否应该跳过某个配置类或 @Bean 方法的加载。
// ConditionEvaluator.shouldSkip() 方法的逻辑摘要public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase phase) {// 1. 如果元数据上没有 @Conditional 注解,直接返回 false(不跳过)if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {return false;}// 2. 如果已经评估过且有缓存结果,直接返回。// 3. 创建一个 Condition 实例列表// 它会获取 @Conditional 注解中指定的 Condition 实现类List<Condition> conditions = new ArrayList<>();for (String[] conditionClasses : getConditionClasses(metadata)) {for (String conditionClass : conditionClasses) {Condition condition = getCondition(conditionClass, this.beanFactory.getBeanClassLoader());conditions.add(condition);}}// 4. 对 Condition 实例进行排序,确保执行顺序(如 PriorityOrdered)// 5. 遍历所有 Condition,调用其 matches 方法进行评估for (Condition condition : conditions) {// 创建一个 ConditionContextImpl,封装了环境、注册表等信息 ConditionContext context = this.context;// 调用 matches 方法进行判断if (!condition.matches(context, metadata)) {// 只要有一个 Condition 返回 false,就意味着条件不匹配// 整个 shouldSkip 方法就返回 true(表示需要跳过)return true; }}// 6. 如果所有 Condition 都返回 true,则 shouldSkip 返回 false(不跳过)return false;
}
ConditionContextImpl 是一个内部类,它实现了 ConditionContext 接口,为 Condition 的 matches 方法提供了评估所需的所有上下文信息,如 BeanFactory、Environment、ResourceLoader 等。
6. 条件的具体实现:OnClassCondition 与 OnBeanCondition
Spring Boot 提供了大量开箱即用的 Condition 实现,它们都遵循上述评估流程。我们以两个最常见的条件为例:
@ConditionalOnClass → OnClassCondition
这个条件用于判断 Classpath 中是否存在指定的类 。其背后的实现是 OnClassCondition。
源码位置: org.springframework.boot.autoconfigure.condition.OnClassCondition
它的 matches 方法最终会调用 getMatchOutcome,其核心逻辑是尝试使用 ClassLoader 加载 @ConditionalOnClass 注解中指定的类名。如果加载成功,则条件匹配;如果抛出 ClassNotFoundException 或 NoClassDefFoundError,则条件不匹配。
@ConditionalOnMissingBean → OnBeanCondition
这个条件用于判断 Spring 容器中是否不存在指定类型的 Bean,常用于提供默认配置 。其背后的实现是 OnBeanCondition。
源码位置: org.springframework.boot.autoconfigure.condition.OnBeanCondition
它的 getMatchOutcome 逻辑更为复杂。它会根据 @ConditionalOnMissingBean 中定义的类型、名称或注解,去 BeanFactory 中查找匹配的 Bean。
- 如果找到了任何匹配的 Bean,则条件不匹配。
- 如果一个都没有找到,则条件匹配。
OnBeanCondition 同时还服务于 @ConditionalOnBean 和 @ConditionalOnSingleCandidate,只是在判断逻辑上略有不同。
7. 最终的 Bean 注册与实例化
经历了以上所有步骤之后:
- 通过 @EnableAutoConfiguration 启动了自动配置。
- AutoConfigurationImportSelector 从 .imports 文件中加载了所有候选配置类。
- ConfigurationClassPostProcessor 开始解析这些配置类。
- 在解析过程中,ConditionEvaluator 对每个配置类及其 @Bean 方法上的 @Conditional 注解进行了评估。
对于所有通过了条件检查的自动配置类,ConfigurationClassParser 会继续解析它们内部的 @Bean 方法。每个 @Bean 方法最终都会被转换成一个 BeanDefinition 对象 。这个 BeanDefinition 包含了创建 Bean 所需的所有元信息(如类名、作用域、构造函数参数、属性值等)。
这些 BeanDefinition 对象会被注册到一个名为 BeanDefinitionRegistry 的注册表中(DefaultListableBeanFactory 是其一个核心实现)。
至此,自动配置的“定义”阶段就完成了。这些 Bean 并没有被立即创建。它们会在后续的某个时间点被实例化,例如:
- 当 Spring 容器完成启动,需要实例化所有非懒加载的单例 Bean 时。
- 当应用程序中的其他组件通过 @Autowired 等方式依赖注入该 Bean 时。
- 当通过 applicationContext.getBean() 方法显式请求该 Bean 时。
8. 完整执行流程图
为了更直观地理解整个过程,以下是自动配置核心链路的调用顺序图:

总结
Spring Boot 的自动配置是一个设计精巧、层次分明的系统。它从 @SpringBootApplication 注解出发,通过 @Import 机制引入 AutoConfigurationImportSelector 作为核心入口。在 Spring Boot 3.x 中,该选择器利用 ImportCandidates 类高效地从 AutoConfiguration.imports 文件中加载所有候选配置。随后,这些配置类在被 ConfigurationClassPostProcessor 和 ConfigurationClassParser 解析时,会经过 ConditionEvaluator 的严格审查,只有满足 @Conditional 条件的配置才会最终生效。最后,通过条件检查的配置类中的 @Bean 方法被解析为 BeanDefinition 并注册到 Spring 容器中,等待被实例化和使用。
以下罗列了一些参考的文章,大家可以看看:
https://cloud.tencent.com/developer/article/1362789
https://www.cnblogs.com/east7/p/10426832.html
https://www.cnblogs.com/riches/p/15309813.html
https://cloud.tencent.com/developer/ask/sof/115945026
https://www.cnblogs.com/yw0219/p/8673011.html
https://www.cnblogs.com/jingzh/p/16080471.html
