Spring Boot 从“会用”到“精通”:自动装配原理

自动装配原理

一、是什么 ------ 自动装配到底是什么?

1. 一句话定义

Spring Boot 自动装配(Auto-Configuration)就是框架在启动时,根据你的依赖和配置,自动把所需的组件注册到 IoC 容器中 ,让你无需手写 XML 配置或 @Bean 方法。

2. 用大白话理解

你去餐厅吃饭:

  • 传统 Spring:给你一张空白菜单,让你自己写菜名、配料、做法。你得知道"鱼香肉丝"需要猪肉、木耳、胡萝卜......少写一样就上不了菜。
  • Spring Boot 自动装配:给你一张印好的菜单(Starter),你只需勾选想吃的菜(引入依赖),后厨自动备料、自动炒菜、自动上桌。你想少放盐?告诉服务员一声就行(application.properties)。

3. 自动装配能帮我们做什么?

自动装配的功能 说明
自动配置 Tomcat 引入 spring-boot-starter-web 自动嵌入 Tomcat
自动配置 Spring MVC DispatcherServlet、ViewResolver、HttpMessageConverter 全套组件自动注册
自动配置 Web 常见功能 字符编码过滤器、文件上传解析器、错误页面等
默认包结构扫描 主程序所在包及其子包自动扫描,无需 @ComponentScan
配置属性绑定 application.properties 的值自动绑定到 XxxProperties

二、为什么 ------ 为什么要这样设计?

1. 解决的核心矛盾

在 Spring Boot 出现之前,即使是一个最简单的"Hello World" Web 项目,也需要:

  1. 配置 web.xml(Servlet 容器)
  2. 配置 applicationContext.xml(Spring 容器)
  3. 配置 spring-mvc.xml(Spring MVC)
  4. 手动注册 DispatcherServlet
  5. 手动配置视图解析器
  6. 手动配置静态资源处理器
  7. ......

而实际项目远比这复杂,加入 MyBatis、Redis、MQ 等中间件后,配置量呈指数级增长。

Spring Boot 的设计目标就是:把所有常见的配置,全部固化成"默认配置",开发者只需关心业务逻辑。

2. 设计哲学:约定大于配置 + 按需加载

  • 约定大于配置:框架默认知道"你的 Controller 在哪个包"、"你的静态资源在哪里"、"你的 Tomcat 端口默认是 8080"。
  • 按需加载 :虽然 Spring Boot 准备了 130+ 个自动配置类,但不是每个都生效 。只有满足 @Conditional 条件的才会真正执行。你引入了 Redis 依赖,Redis 的配置类才激活。

三、怎么做 ------ 自动装配的底层执行流程

1. 一个入口:@SpringBootApplication

一切从启动类上的这个注解开始:

java 复制代码
// 源码位置:springboot2-master/boot-01-helloworld/src/main/java/com/atguigu/boot/MainApplication.java
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
}

@SpringBootApplication 是一个复合注解,等效于:

java 复制代码
@SpringBootConfiguration      // = @Configuration,标记为配置类
@EnableAutoConfiguration       // 自动装配的"灵魂"
@ComponentScan                 // 扫描当前包及子包

2. 三大核心注解拆解

2.1 @SpringBootConfiguration

本质上就是 @Configuration,标记当前类为 Spring 的主配置类。

2.2 @ComponentScan

指定要扫描的包路径。默认扫描主启动类所在包及其所有子包。这就解释了为什么 Controller、Service 只要放在主启动类同级或子级目录,就能被自动发现。

2.3 @EnableAutoConfiguration ------ 自动装配的"灵魂"

这个注解才是自动装配的核心:

java 复制代码
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

它又分解为两个关键部分:

(A)@AutoConfigurationPackage ------ 自动包注册
java 复制代码
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}

利用 Registrar 向容器中导入主启动类所在包下的所有组件:

java 复制代码
// AutoConfigurationPackages.Registrar 核心逻辑
static class Registrar implements ImportBeanDefinitionRegistrar {
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        AutoConfigurationPackages.register(registry, 
            new PackageImports(metadata).getPackageNames().toArray(new String[0]));
    }
}

new PackageImports(metadata).getPackageNames() 返回的就是主启动类所在的包名。

(B)@Import(AutoConfigurationImportSelector.class) ------ 批量导入配置类

这是自动装配的核心发动机。它会通过 SPI 机制加载所有候选的自动配置类。

3. 五步执行流程(源码深度追踪)

第一步:selectImports() 触发选择器
java 复制代码
// AutoConfigurationImportSelector
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    }
    // 核心:获取自动配置条目
    AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
    return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
第二步:getAutoConfigurationEntry() 获取全量候选
java 复制代码
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    // 获取所有候选配置类(130+ 个全类名)
    List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);  // 去重
    Set<String> exclusions = getExclusions(annotationMetadata, attributes); // 排除手动指定的
    configurations.removeAll(exclusions);               // 剔除排除项
    configurations = getConfigurationClassFilter().filter(configurations); // @Conditional 过滤
    return new AutoConfigurationEntry(configurations, exclusions);
}
第三步:getCandidateConfigurations() ------ SPI 加载
java 复制代码
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, 
                                                   AnnotationAttributes attributes) {
    // 通过 SpringFactoriesLoader 加载 META-INF/spring.factories 中配置的组件
    List<String> configurations = new ArrayList<>(
        SpringFactoriesLoader.loadFactoryNames(
            getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()
        )
    );
    return configurations;
}

这里面 SpringFactoriesLoader.loadFactoryNames() 就是 SPI 机制的核心。

第四步:loadSpringFactories() ------ 读取 spring.factories
java 复制代码
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    // 从所有 jar 包的 META-INF/spring.factories 中读取配置
    Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
    // 遍历所有 URL,解析 Properties 文件
    // 将全类名按 factoryType 分组存入 Map
}

关键文件位置(以 2.7.4 为例):

  • spring-boot-autoconfigure-2.7.4.jar!/META-INF/spring.factories
  • spring-boot-2.7.4.jar!/META-INF/spring.factories

这个文件里声明了 130+ 个 xxxAutoConfiguration 的全类名。

第五步:@Conditional 按需过滤

虽然 130+ 个配置类全部被加载了,但不是每个都会生效 。每个 xxxAutoConfiguration 类上都标注了各种 @Conditional 注解:

java 复制代码
// 示例 1:字符编码过滤器 ------ 容器中没有时才自动配置
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
    return new OrderedCharacterEncodingFilter();
}

// 示例 2:文件上传解析器 ------ 有 MultipartResolver 时才注入
@Bean
@ConditionalOnBean(MultipartResolver.class)
@ConditionalOnMissingBean(name = "multipartResolver")
public MultipartResolver multipartResolver(MultipartResolver resolver) {
    return resolver;  // 防止用户配置的文件上传解析器不符合规范
}

4. 一条金线:配置绑定链路

自动装配生效后,配置类需要读取用户在 application.properties 中自定义的参数。整个数据流向:

复制代码
application.properties
    ↓ @ConfigurationProperties(prefix="xxx")
XxxProperties 类(属性持有者)
    ↓ @EnableConfigurationProperties(XxxProperties.class)
XxxAutoConfiguration(自动配置类)
    ↓ @Bean 方法
具体组件 Bean(注入到容器)

实例:文件上传的大小阈值

复制代码
spring.servlet.multipart.file-size-threshold=10MB
    ↓
MultipartProperties 类的 fileSizeThreshold 属性
    ↓
MultipartAutoConfiguration 读取 MultipartProperties
    ↓
配置到 MultipartResolver 组件中

5. 用户自定义覆盖机制

Spring Boot 的优先级设计是:用户优先,默认兜底

  • 如果用户自己用 @Bean 注册了同类型组件,Spring Boot 的 @ConditionalOnMissingBean 就会自动退让
  • 如果用户想修改参数,直接改 application.properties 即可
java 复制代码
// 例如:自定义 HiddenHttpMethodFilter 替换默认的
@Configuration(proxyBeanMethods = false)
public class WebConfig {
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter(){
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_m");  // 把约定参数从 _method 改成 _m
        return methodFilter;
    }
}

四、总结 ------ 一张图看清自动装配全貌

复制代码
@SpringBootApplication
    │
    ├── @SpringBootConfiguration    → 标记为主配置类
    │
    ├── @ComponentScan              → 扫描业务组件
    │
    └── @EnableAutoConfiguration    → 自动装配灵魂
            │
            ├── @AutoConfigurationPackage → 注册主程序包
            │
            └── @Import(AutoConfigurationImportSelector.class)
                    │
                    ├── spring.factories → 加载 130+ 候选配置类
                    │
                    ├── 去重 + 排除
                    │
                    ├── @Conditional 按需过滤
                    │
                    └── 生效的配置类 → @Bean 注册组件 → 容器
                            │
                            └── 组件从 XxxProperties 拿配置值
                                    │
                                    └── 用户在 application.properties 中修改

一句话总结

Spring Boot 预置了海量的 xxxAutoConfiguration 方案,它利用 SPI 机制发现它们,利用 @Conditional 注解按需激活它们,最后通过 xxxProperties 允许开发者在外部微调它们,从而实现了开箱即用的绝佳体验

这正是我在闲聊中总结的"一个入口、三大核心、五步流程与一条金线"。理解了这套机制,再去读任何一个自动配置类的源码,都能快速定位到关键逻辑。

相关推荐
小的~~1 小时前
Java线程及线程池的相关的问题
java·开发语言·多线程
爱吃羊的老虎1 小时前
【JAVA】Java微服务—网关Gateway
java·微服务·gateway
雪隐2 小时前
AI股票小助手05-用 Flask 把 MiniQMT 变成 REST API
人工智能·后端
霸道流氓气质2 小时前
Spring AI Ollama 连接超时问题排查与解决:OkHttp 读超时配置全指南
人工智能·spring·okhttp
人道领域2 小时前
一篇文章解决Codex的安装,实操一遍过
java·开发语言·codex
道友可好2 小时前
Spec Kit:GitHub 官方出品,规范即代码
前端·人工智能·后端
货拉拉技术2 小时前
货拉拉标注平台-拉拉标注
后端·架构
fox_lht2 小时前
第十四章 一个输入和输出项目:构建一个命令行程序
开发语言·后端·rust
郑州光合科技余经理2 小时前
海外版外卖系统:如何快速搭建国际化外卖平台
java·开发语言·前端·人工智能·小程序·系统架构·php