摘要:从一次"手写starter实现自定义组件自动注入"的需求出发,深度剖析SpringBoot自动装配的核心原理。通过@SpringBootApplication三大注解的源码解析、SPI机制的加载流程、以及Conditional条件装配的实战案例,揭秘为什么引入starter依赖就能自动配置、META-INF/spring.factories的作用、以及如何手写一个自定义starter。配合时序图展示启动流程,给出自动装配的最佳实践。
💥 翻车现场
周四下午,哈吉米接到了一个需求。
技术总监:"我们要把Redis配置封装成一个通用组件,其他项目引入依赖就能用,不用每次都写配置类。"
哈吉米:"好的!"(内心:这不就是自定义starter吗?)
一周后……
哈吉米:"总监,我写好了!"
<!-- 其他项目引入依赖 -->
<dependency>
<groupId>com.ajocer</groupId>
<artifactId>redis-starter</artifactId>
<version>1.0.0</version>
</dependency>
测试项目启动:
错误:
Field redisTemplate in com.example.service.UserService required a bean of type 'RedisTemplate' that could not be found.
哈吉米:"卧槽,RedisTemplate没有被注入?明明写了@Configuration啊!"
查看starter代码:
// redis-starter项目
@Configuration
public class RedisAutoConfiguration {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
return template;
}
}
哈吉米:"配置类写了,为什么没有被加载?"
南北绿豆和阿西噶阿西来了。
南北绿豆:"你缺了关键的一步:在META-INF/spring.factories中注册配置类!"
哈吉米:"spring.factories是啥?"
阿西噶阿西:"这是SpringBoot自动装配的核心文件,我给你讲讲自动装配的原理。"
🤔 什么是自动装配?
传统Spring的配置
// 传统Spring(需要手动配置)
@Configuration
public class AppConfig {
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/db");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
@Bean
public RedisTemplate redisTemplate() {
RedisTemplate template = new RedisTemplate();
// 一堆配置...
return template;
}
@Bean
public RabbitTemplate rabbitTemplate() {
// 一堆配置...
}
// ... 每个组件都要手动配置
}
问题:
- ❌ 配置繁琐
- ❌ 每个项目都要写重复的配置
- ❌ 容易出错
SpringBoot的自动装配
// SpringBoot(自动装配)
// 引入依赖后,啥都不用配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
// application.yml只需要写连接信息
spring:
redis:
host: localhost
port: 6379
// 代码直接注入使用
@Autowired
private RedisTemplate redisTemplate; // 自动注入 ✅
优点:
- ✅ 约定优于配置
- ✅ 开箱即用
- ✅ 简化开发
阿西噶阿西:"这就是自动装配的威力!引入依赖就能用,不用手动配置。"
🎯 自动装配的核心原理
@SpringBootApplication三大注解
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// @SpringBootApplication等价于三个注解
@SpringBootConfiguration // 等价于@Configuration
@EnableAutoConfiguration // 核心:开启自动装配
@ComponentScan // 扫描@Component、@Service等
public @interface SpringBootApplication {
// ...
}
南北绿豆:"核心是 @EnableAutoConfiguration,它触发了自动装配。"
@EnableAutoConfiguration的源码
@Import(AutoConfigurationImportSelector.class) // 关键
public @interface EnableAutoConfiguration {
// ...
}
// AutoConfigurationImportSelector
public class AutoConfigurationImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
// 1. 加载所有的自动配置类
List<String> configurations = getCandidateConfigurations(metadata, attributes);
// 2. 去重
configurations = removeDuplicates(configurations);
// 3. 排序
configurations = sort(configurations);
// 4. 过滤(根据@Conditional条件)
configurations = filter(configurations);
// 5. 返回配置类的全限定名
return configurations.toArray(new String[0]);
}
protected List<String> getCandidateConfigurations(...) {
// 关键:从META-INF/spring.factories加载配置类
return SpringFactoriesLoader.loadFactoryNames(
EnableAutoConfiguration.class,
classLoader
);
}
}
从META-INF/spring.factories加载
spring-boot-autoconfigure.jar的spring.factories:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
... (130多个自动配置类)
加载流程图:
graph TD
A[启动SpringBoot应用] --> B[@SpringBootApplication]
B --> C[@EnableAutoConfiguration]
C --> D[AutoConfigurationImportSelector]
D --> E[读取META-INF/spring.factories]
E --> F[加载130+个自动配置类]
F --> G{检查@Conditional条件}
G -->|条件满足| H[注册Bean到容器]
G -->|条件不满足| I[跳过]
H --> J[RedisAutoConfiguration]
H --> K[DataSourceAutoConfiguration]
H --> L[...]
J --> M[创建RedisTemplate]
K --> N[创建DataSource]
style H fill:#90EE90
style I fill:#FFB6C1
哈吉米:"所以SpringBoot启动时,会自动加载所有starter的配置类?"
南北绿豆:"对!但不是全部生效,要看**@Conditional条件**。"
🎯 @Conditional条件装配
常用条件注解
| 注解 | 含义 |
|---|---|
| @ConditionalOnClass | classpath中存在指定类才生效 |
| @ConditionalOnMissingClass | classpath中不存在指定类才生效 |
| @ConditionalOnBean | 容器中存在指定Bean才生效 |
| @ConditionalOnMissingBean | 容器中不存在指定Bean才生效 |
| @ConditionalOnProperty | 配置文件中有指定属性才生效 |
RedisAutoConfiguration示例
// Redis自动配置类(简化)
@Configuration
@ConditionalOnClass(RedisOperations.class) // 1. classpath有RedisOperations类
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate") // 2. 容器中没有redisTemplate
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
return template;
}
}
生效条件:
1. 引入了redis依赖(有RedisOperations类)✅
2. 用户没有自定义redisTemplate(没有同名Bean)✅
→ 自动配置生效,创建默认的RedisTemplate
如果用户自定义了:
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(...) {
// 用户自定义的配置
return customTemplate;
}
}
// 结果:
// @ConditionalOnMissingBean生效,自动配置不生效
// 使用用户自定义的RedisTemplate
阿西噶阿西:"这就是约定优于配置的体现:有默认配置,也可以自定义覆盖。"
🎯 手写一个自定义starter
需求
封装短信发送组件:
功能:
- 支持阿里云短信、腾讯云短信
- 引入依赖后自动配置
- 可以通过配置文件切换
步骤1:创建starter项目
项目结构:
sms-spring-boot-starter/
├── pom.xml
└── src/main/java/
└── com/ajocer/sms/
├── SmsAutoConfiguration.java
├── SmsProperties.java
├── SmsService.java
└── impl/
├── AliyunSmsServiceImpl.java
└── TencentSmsServiceImpl.java
└── src/main/resources/
└── META-INF/
└── spring.factories ← 关键文件
步骤2:编写配置属性类
@ConfigurationProperties(prefix = "sms")
public class SmsProperties {
private String type = "aliyun"; // 默认阿里云
private String accessKey;
private String accessSecret;
private String signName;
// getter/setter
}
步骤3:编写自动配置类
@Configuration
@EnableConfigurationProperties(SmsProperties.class) // 启用配置属性
@ConditionalOnClass(SmsService.class) // classpath中有SmsService
public class SmsAutoConfiguration {
@Autowired
private SmsProperties properties;
/**
* 阿里云短信(条件:配置文件中sms.type=aliyun)
*/
@Bean
@ConditionalOnProperty(name = "sms.type", havingValue = "aliyun", matchIfMissing = true)
public SmsService aliyunSmsService() {
return new AliyunSmsServiceImpl(
properties.getAccessKey(),
properties.getAccessSecret(),
properties.getSignName()
);
}
/**
* 腾讯云短信(条件:配置文件中sms.type=tencent)
*/
@Bean
@ConditionalOnProperty(name = "sms.type", havingValue = "tencent")
public SmsService tencentSmsService() {
return new TencentSmsServiceImpl(
properties.getAccessKey(),
properties.getAccessSecret(),
properties.getSignName()
);
}
}
步骤4:创建META-INF/spring.factories
# src/main/resources/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ajocer.sms.SmsAutoConfiguration
这是关键:SpringBoot通过这个文件知道要加载哪些配置类。
步骤5:使用starter
<!-- 业务项目引入依赖 -->
<dependency>
<groupId>com.ajocer</groupId>
<artifactId>sms-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
配置文件:
# application.yml
sms:
type: aliyun
access-key: your-key
access-secret: your-secret
sign-name: your-sign
业务代码:
@Service
public class UserService {
@Autowired
private SmsService smsService; // 自动注入 ✅
public void register(User user) {
// 发送短信
smsService.send(user.getPhone(), "验证码:123456");
}
}
哈吉米:"什么都不用配置,引入依赖就能用!这就是自动装配!"
🎯 自动装配的完整流程
启动时序图
sequenceDiagram
participant Main as main方法
participant SpringApp as SpringApplication
participant Selector as AutoConfigurationImportSelector
participant SPI as SpringFactoriesLoader
participant Config as 配置类
Main->>SpringApp: 1. SpringApplication.run()
SpringApp->>SpringApp: 2. 创建ApplicationContext
SpringApp->>Selector: 3. 处理@EnableAutoConfiguration
Selector->>SPI: 4. 加载META-INF/spring.factories
SPI->>SPI: 5. 读取所有starter的spring.factories
SPI->>Selector: 6. 返回130+个配置类名称
loop 遍历配置类
Selector->>Config: 7. 加载配置类
Config->>Config: 8. 检查@Conditional条件
alt 条件满足
Config->>SpringApp: 9. 注册Bean到容器
else 条件不满足
Config->>Config: 10. 跳过
end
end
SpringApp->>Main: 11. 启动完成
详细步骤
步骤1:扫描所有jar包的spring.factories
扫描路径:
- spring-boot-autoconfigure.jar
- META-INF/spring.factories(130+个配置类)
- mybatis-spring-boot-starter.jar
- META-INF/spring.factories(MyBatis配置类)
- redis-spring-boot-starter.jar
- META-INF/spring.factories(Redis配置类)
- 自定义sms-starter.jar
- META-INF/spring.factories(自定义配置类)
步骤2:加载配置类,检查@Conditional
// Redis配置类
@Configuration
@ConditionalOnClass(RedisOperations.class) // 检查:是否引入了Redis依赖?
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean // 检查:用户是否自定义了RedisTemplate?
public RedisTemplate redisTemplate(...) {
// 自动配置
}
}
// 检查结果:
// 1. 引入了redis依赖 ✅
// 2. 用户没有自定义 ✅
// → 配置生效,创建RedisTemplate
步骤3:注册Bean到容器
Spring容器:
- RedisTemplate(自动装配)
- DataSource(自动装配)
- RabbitTemplate(自动装配)
- SmsService(自定义starter)
- ...
🎯 @Conditional的常用场景
场景1:根据配置文件决定
@Configuration
public class SmsConfig {
@Bean
@ConditionalOnProperty(name = "sms.enabled", havingValue = "true")
public SmsService smsService() {
return new SmsServiceImpl();
}
}
// application.yml
sms:
enabled: true ← 配置为true,才创建SmsService
场景2:根据依赖是否存在
@Configuration
@ConditionalOnClass(name = "com.aliyun.oss.OSS") // 引入了阿里云OSS依赖
public class OssConfig {
@Bean
public OssService ossService() {
return new AliyunOssServiceImpl();
}
}
场景3:根据环境变量
@Configuration
@ConditionalOnProperty(name = "spring.profiles.active", havingValue = "prod")
public class ProdConfig {
@Bean
public DataSource prodDataSource() {
// 生产环境数据源
}
}
场景4:用户自定义优先
@Configuration
public class DefaultConfig {
@Bean
@ConditionalOnMissingBean // 用户没有自定义,才用默认配置
public RedisTemplate defaultRedisTemplate() {
return new RedisTemplate();
}
}
// 用户自定义
@Configuration
public class MyConfig {
@Bean
public RedisTemplate myRedisTemplate() {
// 用户自定义的配置(优先级更高)
return customTemplate;
}
}
// 结果:使用myRedisTemplate,defaultRedisTemplate不生效
🎯 自动装配 vs 手动配置
对比
| 特性 | 手动配置 | 自动装配 |
|---|---|---|
| 配置量 | 多 | 少(约定优于配置) |
| 灵活性 | 高 | 中(可以自定义覆盖) |
| 开发效率 | 低 | 高 |
| 适用场景 | 复杂定制 | 通用组件 |
何时用自动装配,何时手动配置?
用自动装配:
✅ 通用组件(Redis、MQ、数据源)
✅ 标准配置(大部分项目都一样)
✅ 快速开发(原型、小项目)
用手动配置:
✅ 复杂定制(特殊需求)
✅ 多数据源(需要精细控制)
✅ 性能敏感(需要深度优化)
阿西噶阿西:"一般情况下,优先用自动装配,有特殊需求再手动覆盖。"
🎓 面试标准答案
题目:SpringBoot自动装配的原理是什么?
答案:
核心流程:
- @SpringBootApplication 包含 @EnableAutoConfiguration
- @EnableAutoConfiguration 导入 AutoConfigurationImportSelector
- AutoConfigurationImportSelector 从 META-INF/spring.factories 加载配置类
- 加载所有starter的自动配置类(130+个)
- 根据 @Conditional 条件过滤(类是否存在、Bean是否存在、配置是否存在)
- 符合条件的配置类注册到Spring容器
- 创建Bean,完成自动装配
关键文件:
META-INF/spring.factories:配置自动装配的类@Conditional注解:条件判断
核心思想:
- 约定优于配置
- 开箱即用
- 可以自定义覆盖
题目:如何自定义一个starter?
答案:
5个步骤:
- 创建starter项目
- 编写配置属性类(@ConfigurationProperties)
- 编写自动配置类(@Configuration + @Conditional)
- 创建META-INF/spring.factories(注册配置类)
- 打包发布
使用:
- 引入依赖
- 配置属性(application.yml)
- 直接注入使用
🎉 结束语
晚上10点,哈吉米终于把自定义starter写好了。
哈吉米:"原来只是少了META-INF/spring.factories这个文件,导致配置类没有被加载!"
南北绿豆:"对,spring.factories是SpringBoot自动装配的关键文件。"
阿西噶阿西:"记住:自动装配 = SPI加载 + 条件过滤 + Bean注册。"
哈吉米:"还有@Conditional注解,可以根据类、Bean、配置文件等条件决定是否生效。"
南北绿豆:"对,理解了自动装配,就理解了SpringBoot的核心设计思想!"
记忆口诀:
SpringBoot自动装配妙,SPI机制是基础
spring.factories注册类,Conditional条件过滤
引入依赖就能用,约定优于配置
自定义starter五步走,封装组件很方便
用户自定义优先级,默认配置可覆盖
希望这篇文章能帮你彻底理解SpringBoot自动装配!记住:自动装配让开发更简单,理解原理让你能自定义组件!💪