SpringBoot自动配置AutoConfiguration原理与实践

AutoConfiguration 的生效原理

AutoConfigurationImportSelector在@SpringBootApplication下的@EnableAutoConfiguration中被导入:@Import({AutoConfigurationImportSelector.class})

自动配置生效的核心流程调用链如下:
AutoConfigurationImportSelector.selectImports -> AutoConfigurationImportSelector.getAutoConfigurationEntry -> AutoConfigurationImportSelector.getCandidateConfigurations -> ImportCandidates.load

1. 入口:AutoConfigurationImportSelector.selectImports

这是 DeferredImportSelector 接口定义的入口方法,Spring 容器启动时会调用它。

源码分析

java 复制代码
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    // 1. 检查是否开启了自动配置(一般都是 true)
    if (!isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    
    // 2. 【核心】获取自动配置项的实体对象
    // 这步执行完,所有的类名(包括官方的和你的)都已经找齐了
    AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    
    // 3. 将结果转为 String 数组返回给容器,Spring 容器随后会把这些类加载为 Bean
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}

运行时变量快照 (Example)

  • annotationMetadata : 代表你的启动类 YudaoServerApplication 的元数据。
  • autoConfigurationEntry : 包含了一个 List,例如 ["...RedisAutoConfiguration", "...YudaoCacheAutoConfiguration", ...],共计 140+ 个类名。

2. 调度核心:AutoConfigurationImportSelector.getAutoConfigurationEntry

这个方法负责指挥"搜集 -> 去重 -> 排除 -> 过滤"的全过程。

源码分析

java 复制代码
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    // 1. 获取是否配置了 exclude 等属性
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    
    // 2. 【核心调用】获取所有候选配置类的全限定名
    // 这一步会去扫描所有 jar 包下的配置文件
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    
    // 3. 去重(防止同一个 jar 包被引了两次导致重复)
    configurations = removeDuplicates(configurations);
    
    // 4. 处理排除逻辑(比如你在 @SpringBootApplication(exclude=...) 里写的类)
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    
    // 5. 再次过滤(根据 onClass 等条件进行过滤,注意这里只是初步过滤)
    configurations = getConfigurationClassFilter().filter(configurations);
    
    // 6. 发送事件(通知监听器,自动配置类已经拿到了)
    fireAutoConfigurationImportEvents(configurations, exclusions);
    
    return new AutoConfigurationEntry(configurations, exclusions);
}

运行时变量快照 (Example)

  • configurations (步骤2执行后) : 这是一个原始的巨大列表,包含 org.springframework.boot.autoconfigure.data.redis.RedisAutoConfigurationcn.iocoder...YudaoCacheAutoConfiguration
  • exclusions : 假如你没配置排除,这里是 Empty Set

3. 双轨加载:AutoConfigurationImportSelector.getCandidateConfigurations

这一步是 Spring Boot 2.7+ / 3.x 兼容性的体现,它同时加载新旧两种配置文件。

源码分析

java 复制代码
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 1. 【加载旧版】加载 spring.factories
    List<String> configurations = new ArrayList<>(
        SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader())
    );
    
    // 2. 【加载新版】加载 .imports 文件
    // 这一步是读取 META-INF/spring/...imports
    ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader())
        .forEach(configurations::add); // 将新版读到的结果追加到 list 中
    
    // 3. 断言非空(如果一个都没找到,说明 jar 包损坏或环境有问题)
    Assert.notEmpty(configurations, "No auto configuration classes found...");
    
    return configurations;
}

运行时变量快照 (Example)

  • configurations (步骤1后) : 包含从 spring.factories 读到的旧版配置类(如果是纯 Spring Boot 3.0+ 项目,这里可能是空的或很少)。
  • configurations (步骤2后) : 追加了从 .imports 文件读到的新版配置类。此时你的 YudaoCacheAutoConfiguration 被加进来了。

4. 物理扫描:ImportCandidates.load

这是最底层的 IO 读取逻辑。

源码分析

java 复制代码
public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
    // 1. 确定类加载器
    ClassLoader classLoaderToUse = decideClassloader(classLoader);
    
    // 2. 【拼接路径】
    // annotation.getName() = "org.springframework.boot.autoconfigure.AutoConfiguration"
    // 结果 location = "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports"
    String location = String.format("META-INF/spring/%s.imports", annotation.getName());
    
    // 3. 【物理查找】
    // 调用 ClassLoader.getResources(location) 
    // 去所有 jar 包里找这个路径的文件,返回一组 URL
    Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
    
    List<String> importCandidates = new ArrayList<>();

    // 4. 【循环读取】
    while(urls.hasMoreElements()) {
        URL url = (URL)urls.nextElement();
        // 打开每一个 URL 指向的文件,逐行读取里面的字符串(类名)
        // 并把它们全部 add 到 importCandidates 集合里
        importCandidates.addAll(readCandidateConfigurations(url));
    }

    return new ImportCandidates(importCandidates);
}

运行时变量快照 (具体的例子)

这里我们还原一下 ImportCandidates.load 运行时各个变量的真实值:

1. annotation:

  • 值:org.springframework.boot.autoconfigure.AutoConfiguration.class

2. location:

  • 值:"META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports"

3. urls (Enumeration 对象) :

假设你的项目依赖了官方包和你的 yudao 包,这个枚举里会包含两个 URL 对象:

  • URL 1 : jar:file:/Users/repo/.../spring-boot-autoconfigure-2.7.18.jar!/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  • URL 2 : jar:file:/Users/repo/.../yudao-spring-boot-starter-redis-1.0.jar!/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

4. readCandidateConfigurations(url) 的返回值:

  • 当读取 URL 1 时 :返回一个 List,包含 RedisAutoConfiguration, WebMvcAutoConfiguration 等 100 多个官方类名。
  • 当读取 URL 2 时 :返回一个 List,包含 cn.iocoder...YudaoCacheAutoConfiguration 这 1 个类名。

5. importCandidates (最终返回值):

  • 这是一个合并后的大 List:[...官方的100个..., "cn.iocoder...YudaoCacheAutoConfiguration"]


这是一篇为您准备的技术博客,深入剖析 Spring Boot 自动配置的"失效"(即自动退让)机制。


AutoConfiguration 的失效原理

------ 为什么自定义 Bean 会让默认配置自动"闭嘴"?

在使用 Spring Boot 时,我们经常会遇到这种"魔法"现象:

  • 什么都不配置,Spring Boot 默认为我们配置好了 Redis、DataSource、MVC。
  • 一旦我们在项目中手动定义了一个 RedisTemplateDataSource,Spring Boot 的默认配置就瞬间"消失"了,转而使用我们的配置。

这种约定大于配置用户优先的机制是如何实现的?

一、 核心机制:防御性注解

Spring Boot 能够实现"你配了我就不配,你没配我来兜底"的效果,核心在于一个关键的条件注解:@ConditionalOnMissingBean

这个注解直译过来就是:"仅当容器中缺失(Missing)指定 Bean 时,条件才成立。"

1. 源码证据

让我们看一眼 RedisCacheConfiguration(Spring Boot 官方自动配置类)的源码:

java 复制代码
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisConnectionFactory.class)
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisConnectionFactory.class)
@ConditionalOnMissingBean(CacheManager.class) // <--- 【核心开关】
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, ...) {
        // 创建默认的 RedisCacheManager
        return RedisCacheManager.builder(redisConnectionFactory).build();
    }
}

注意那行 @ConditionalOnMissingBean(CacheManager.class)。它的逻辑非常简单粗暴:

Spring 容器,请检查一下你自己,现在有没有一个类型为 CacheManager 的 Bean?

  • 如果没有 :好的,执行下面的 @Bean 方法,创建一个默认的。
  • 如果有:打扰了,我不执行了。

二、 关键维度:加载时机 (Timing)

仅有注解是不够的。如果自动配置类比用户的配置类先执行 ,那容器里肯定没有 Bean,@ConditionalOnMissingBean 就会误判,导致默认配置总是生效,覆盖了用户的配置。

因此,加载顺序是"失效原理"的另一个核心支柱。

1. DeferredImportSelector (延迟导入)

我们在之前分析过,@EnableAutoConfiguration 导入的是 AutoConfigurationImportSelector

注意这个类的接口定义:

java 复制代码
public class AutoConfigurationImportSelector implements DeferredImportSelector { ... }

Deferred (延迟) 是关键词。在 Spring 的生命周期中,实现了 DeferredImportSelector 的类,会在所有的标准 @Configuration 类(也就是用户你自己写的配置)被解析和注册之后,才开始执行。

2. 完美的时序配合

Spring Boot 的启动流程经过精心设计,确保了"用户优先":

  1. Phase 1: 用户配置加载期
  • Spring 扫描你的包结构。
  • 发现你定义的 @Configuration 类(例如 YudaoCacheAutoConfiguration)。
  • 执行其中的 @Bean 方法,将你自定义的 RedisCacheManager 注册到 IoC 容器中。
  • 此时:容器里已有 1 个 CacheManager。
  1. Phase 2: 自动配置加载期 (Deferred)
  • 用户配置全部处理完后,Spring 开始处理 AutoConfiguration
  • 加载器读取 META-INF/.../AutoConfiguration.imports
  • 找到官方的 RedisCacheConfiguration
  1. Phase 3: 条件评估期
  • Spring 准备加载官方默认的 cacheManager Bean。
  • 触发 @ConditionalOnMissingBean(CacheManager.class) 检查。
  • 检查结果:容器中已存在(Phase 1 注册的那个)。
  • 最终决策:放弃加载官方 Bean。

三、 特殊情况:层级与泛型

@ConditionalOnMissingBean 还有两个需要注意的高级特性,这在自定义 Starter 时非常有用。

1. 按名称失效 (Name)

你可以指定 Bean 的名称,而不是类型。

java 复制代码
// 只有当容器里没有叫 "myRedisTemplate" 的 Bean 时才生效
@ConditionalOnMissingBean(name = "myRedisTemplate")

2. 按泛型失效 (Generics)

对于 RedisTemplate<K, V> 这种带泛型的类,Spring Boot 甚至能识别泛型参数。

官方源码示例:

java 复制代码
@Bean
@ConditionalOnMissingBean(name = "redisTemplate") // 只要没这个名字的
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(...) { ... }

如果不指定泛型,@ConditionalOnMissingBean(RedisTemplate.class) 会把 RedisTemplate<String, String>RedisTemplate<Object, Object> 视为同一个类型。如果你想精确控制(比如只针对 <Object, Object> 类型的缺失做回退),需要在注解中详细配置。



实战如何定制自己的 AutoConfiguration 及生效原理

在微服务架构或中台建设中,我们经常需要封装公司内部的通用组件(如:统一鉴权、日志SDK、自定义缓存策略)。如果每个项目都去复制粘贴代码,维护成本极高。

最佳实践是开发一个自定义的 Starter 。我们将通过一个完整的示例,演示如何编写自己的 AutoConfiguration,并解析它到底是如何被 Spring Boot 发现并接管的。

一、 核心目标

我们要实现一个简单的功能模块:YudaoWelcomeService

  • 功能:打印一条欢迎日志。
  • 配置 :可以通过 application.yml 配置开关和欢迎语。
  • 效果:引入 Jar 包后,无需任何 Java 配置,开箱即用。

二、 步骤详解 (The "How")

步骤 1:定义配置属性类 (Properties)

首先,定义一个 POJO 来承接 application.yml 中的配置项。这体现了 "约定大于配置" 的思想。

java 复制代码
package cn.iocoder.yudao.framework.welcome.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

// 1. 指定配置文件的前缀,例如 yudao.welcome.msg = "Hello"
@ConfigurationProperties(prefix = "yudao.welcome")
public class YudaoWelcomeProperties {

    /**
     * 是否启用
     */
    private boolean enabled = true;

    /**
     * 欢迎语内容
     */
    private String msg = "Welcome to Yudao System!";

    // 省略 Getter 和 Setter
}

步骤 2:编写业务逻辑类 (Service)

这是我们要自动注入到容器里的核心功能 Bean。

java 复制代码
package cn.iocoder.yudao.framework.welcome.core;

public class YudaoWelcomeService {
    
    private String msg;

    public YudaoWelcomeService(String msg) {
        this.msg = msg;
    }

    public void sayHello() {
        System.out.println(">>> Yudao System: " + msg);
    }
}

步骤 3:编写自动配置类 (AutoConfiguration)

这是最关键的装配车间。我们将在这里组装 Bean,并加上条件判断。

java 复制代码
package cn.iocoder.yudao.framework.welcome.config;

import cn.iocoder.yudao.framework.welcome.core.YudaoWelcomeService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

// 1. 标记这是一个自动配置类
@AutoConfiguration
// 2. 激活属性绑定,将 YudaoWelcomeProperties 注册为 Bean
@EnableConfigurationProperties(YudaoWelcomeProperties.class)
// 3. 总开关:只有配置中 yudao.welcome.enabled = true 时才生效(默认为 true)
@ConditionalOnProperty(prefix = "yudao.welcome", name = "enabled", havingValue = "true", matchIfMissing = true)
public class YudaoWelcomeAutoConfiguration {

    @Bean
    public YudaoWelcomeService yudaoWelcomeService(YudaoWelcomeProperties properties) {
        // 利用 Properties 中的值创建 Service
        return new YudaoWelcomeService(properties.getMsg());
    }
}

步骤 4:注册身份 (The Imports File)

这是让 Spring Boot 发现你的配置类的入场券。如果没有这一步,你的类只是 Jar 包里的一个普通文件,Spring 根本不会看它一眼。

src/main/resources 目录下创建文件:
路径META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

内容(写入你的全限定类名):

text 复制代码
cn.iocoder.yudao.framework.welcome.config.YudaoWelcomeAutoConfiguration

三、 生效原理深度解析 (The "Why")

为什么做完上面四步,引入 Jar 包就能生效?这背后是 Spring Boot 启动时的一套严密的 SPI (Service Provider Interface) 机制。

1. 搜集阶段:全网扫描

当你的应用启动,@SpringBootApplication 包含的 @EnableAutoConfiguration 开始工作。

它会召唤 AutoConfigurationImportSelector,利用 ClassLoader 对 Classpath 下所有的 Jar 包发出指令:

"把你们家里 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件里的名单都交上来!"

你的 yudao-framework.jar 响应了号召,交出了 YudaoWelcomeAutoConfiguration 这个名字。

2. 加载阶段:内存融合

Spring 将搜集到的成百上千个配置类名放入一个巨大的列表中。此时,你的配置类和官方的 RedisAutoConfigurationWebMvcAutoConfiguration 处于同等地位,都在等待被加载。

3. 筛选阶段:条件过滤

Spring 开始遍历这个列表,尝试加载每一个配置类。此时,你的类头上的注解开始发挥作用:

  • @ConditionalOnProperty : Spring 检查 application.yml。如果用户设置了 yudao.welcome.enabled=false,你的配置类在这一步就会被"踢出局",Bean 不会被创建。
  • @EnableConfigurationProperties : Spring 发现需要属性绑定,于是自动把 YudaoWelcomeProperties 注册进容器,并填充配置文件里的值。

4. 注册阶段:Bean 的诞生

如果所有条件都满足,Spring 就会执行 @Bean 方法,new YudaoWelcomeService(...),并将实例放入 IoC 容器。


四、 最佳实践 Tips

  1. 命名规范
  • 自动配置类建议以 AutoConfiguration 结尾。
  • 属性类建议以 Properties 结尾。
  1. 模块分离
  • Starter 模块 (yudao-spring-boot-starter-xxx):通常是一个空项目,只负责引入依赖(pom.xml)。
  • Autoconfigure 模块 (yudao-spring-boot-autoconfigure):存放真正的代码(上面的 Java 代码)。
  • 这样做是为了让用户只需要引一个 starter 依赖,就能把所有东西拉下来,同时保持依赖树清晰。
  1. 使用 @ConditionalOnMissingBean
  • 如果你开发的通用组件允许用户覆盖,务必在你的 @Bean 上加上 @ConditionalOnMissingBean
  • 这样,如果用户自己在代码里手动 new 了一个 Service,你的默认配置就会自动礼貌退让,避免冲突。
相关推荐
张较瘦_2 小时前
JavaScript | 数组方法实战教程:push()、forEach()、filter()、sort()
开发语言·javascript·ecmascript
Filotimo_2 小时前
EntityGraph的概念
java·开发语言·数据库·oracle
wregjru2 小时前
【读书笔记】Effective C++ 条款1~2 核心编程准则
java·开发语言·c++
heartbeat..3 小时前
Servlet 全面解析(JavaWeb 核心)
java·网络·后端·servlet
lingran__3 小时前
C语言自定义类型详解 (1.1w字版)
c语言·开发语言
vx_bisheyuange3 小时前
基于SpringBoot的疗养院管理系统
java·spring boot·后端
京东零售技术3 小时前
2025京东零售技术年度精选 | 技术干货篇(内含福利)
前端·javascript·后端
while(1){yan}3 小时前
使用HuTool实现验证码
spring boot·spring·java-ee·maven
村口曹大爷3 小时前
JDK 24 正式发布:性能压轴,为下一代 LTS 铺平道路
java·开发语言