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,你的默认配置就会自动礼貌退让,避免冲突。
相关推荐
红尘散仙4 小时前
我把终端小说阅读器接上了 AI Agent:TRNovel 现在能用 skill 生成书源了
人工智能·后端·rust
来杯@Java5 小时前
图书管理系统(基于springboot+vue前后端分离的项目)计算机毕业设计java
java·spring boot·spring·vue·毕业设计·mybatis·课程设计
卷毛的技术笔记6 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
isyangli_blog6 小时前
OpenDayLight (Carbon 版本) 启动与组件安装
开发语言·php
vb2008116 小时前
FastAPI APIRouter
开发语言·python
Benszen6 小时前
KVM虚拟化解决方案
开发语言·perl
会编程的土豆6 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
東雪木6 小时前
多线程与并发编程 专属复习笔记
java·开发语言·笔记·java面试
喵个咪6 小时前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
杨充6 小时前
1.3 浮点型数据设计灵魂
开发语言·python·算法