SpringBoot自动装配它到底有什么用?

摘要:从一次"手写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条件装配

常用条件注解

注解含义
@ConditionalOnClassclasspath中存在指定类才生效
@ConditionalOnMissingClassclasspath中不存在指定类才生效
@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自动装配的原理是什么?

答案

核心流程

  1. @SpringBootApplication 包含 @EnableAutoConfiguration
  2. @EnableAutoConfiguration 导入 AutoConfigurationImportSelector
  3. AutoConfigurationImportSelectorMETA-INF/spring.factories 加载配置类
  4. 加载所有starter的自动配置类(130+个)
  5. 根据 @Conditional 条件过滤(类是否存在、Bean是否存在、配置是否存在)
  6. 符合条件的配置类注册到Spring容器
  7. 创建Bean,完成自动装配

关键文件

  • META-INF/spring.factories:配置自动装配的类
  • @Conditional 注解:条件判断

核心思想

  • 约定优于配置
  • 开箱即用
  • 可以自定义覆盖

题目:如何自定义一个starter?

答案

5个步骤

  1. 创建starter项目
  2. 编写配置属性类(@ConfigurationProperties)
  3. 编写自动配置类(@Configuration + @Conditional)
  4. 创建META-INF/spring.factories(注册配置类)
  5. 打包发布

使用

  • 引入依赖
  • 配置属性(application.yml)
  • 直接注入使用

🎉 结束语

晚上10点,哈吉米终于把自定义starter写好了。

哈吉米:"原来只是少了META-INF/spring.factories这个文件,导致配置类没有被加载!"

南北绿豆:"对,spring.factories是SpringBoot自动装配的关键文件。"

阿西噶阿西:"记住:自动装配 = SPI加载 + 条件过滤 + Bean注册。"

哈吉米:"还有@Conditional注解,可以根据类、Bean、配置文件等条件决定是否生效。"

南北绿豆:"对,理解了自动装配,就理解了SpringBoot的核心设计思想!"


记忆口诀

SpringBoot自动装配妙,SPI机制是基础
spring.factories注册类,Conditional条件过滤
引入依赖就能用,约定优于配置
自定义starter五步走,封装组件很方便
用户自定义优先级,默认配置可覆盖


希望这篇文章能帮你彻底理解SpringBoot自动装配!记住:自动装配让开发更简单,理解原理让你能自定义组件!💪