自动装配原理
一、是什么 ------ 自动装配到底是什么?
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 项目,也需要:
- 配置
web.xml(Servlet 容器) - 配置
applicationContext.xml(Spring 容器) - 配置
spring-mvc.xml(Spring MVC) - 手动注册
DispatcherServlet - 手动配置视图解析器
- 手动配置静态资源处理器
- ......
而实际项目远比这复杂,加入 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.factoriesspring-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允许开发者在外部微调它们,从而实现了开箱即用的绝佳体验。
这正是我在闲聊中总结的"一个入口、三大核心、五步流程与一条金线"。理解了这套机制,再去读任何一个自动配置类的源码,都能快速定位到关键逻辑。