Lesson 56 · Spring 生态核心原理

Spring Boot 自动装配:@EnableAutoConfiguration 原理

高级·🔥 极高·#Spring Boot·#自动装配

第 1 站

面试官:Spring Boot 为什么能"开箱即用"?

"面试官:你项目里用 Spring Boot,那你说说 Spring Boot 为什么能'开箱即用'?自动装配的原理是什么?你自己写过 Starter 吗?"

这三个问题是 Spring Boot 面试的"三连击",层层递进。大多数候选人能说出"约定大于配置",但真正能把自动装配链路讲清楚的并不多。让我们从一个最常见的现象入手:

一个"零配置"的 Web 应用
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}
// 没有 web.xml,没有 DispatcherServlet 配置,没有内嵌 Tomcat 配置
// 但启动后:Tomcat 跑在 8080,DispatcherServlet 已注册,JSON 序列化就绪
// 这一切是怎么发生的?

答案就藏在一个注解里:@SpringBootApplication。它不是单一注解,而是一个"组合拳"——三个核心注解的聚合体。让我们逐层拆解。

面试为什么必考自动装配?

自动装配是 Spring Boot 的核心机制,理解它意味着你不仅会"用"框架,还理解框架"怎么帮你做事"。面试官通过这个问题可以判断:你是否具备阅读框架源码的能力、是否能自定义 Starter 扩展框架、是否在遇到问题时能从原理层面定位而非只会搜索报错信息。

第 2 站

@SpringBootApplication:三位一体的组合注解

打开 @SpringBootApplication 的源码,你会看到它身上贴了三个核心注解:

源码 — @SpringBootApplication
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration      // ① 本质就是 @Configuration,标记当前类是配置类
@EnableAutoConfiguration      // ② 开启自动装配 —— 今天的核心主角
@ComponentScan(               // ③ 扫描当前包及其子包下的 @Component
    excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)
    }
)
public @interface SpringBootApplication {
    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};  // 排除指定的自动配置类

    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};
}
注解作用面试重点
@SpringBootConfiguration 标记当前类为配置类(等同于 @Configuration 让 Spring Boot 知道从哪里开始加载配置
@EnableAutoConfiguration 开启自动装配,从 META-INF 中加载所有自动配置类 核心机制,后面三站全部围绕它展开
@ComponentScan 扫描启动类所在包及子包下的组件 为什么启动类要放在根包下

面试官追问:为什么启动类一般放在根包下?

@ComponentScan 默认扫描当前包及其所有子包。如果启动类放在 com.example.app 下,那么 com.example.app.servicecom.example.app.controller 都会被扫到。如果放在 com.example.app.controller 包下,service 包里的 Bean 就注册不进来。

第 3 站

@EnableAutoConfiguration:自动装配的加载链路

这是整个自动装配的核心。让我们打开 @EnableAutoConfiguration 的源码:

源码 — @EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage    // 记录自动配置的基础包路径
@Import(AutoConfigurationImportSelector.class)  // ← 关键!导入选择器
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

核心在于 @Import(AutoConfigurationImportSelector.class)。这个 Selector 负责从 classpath 下加载所有自动配置类。加载链路如下:

AutoConfigurationImportSelector 加载链路(简化)
public class AutoConfigurationImportSelector
        implements DeferredImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata metadata) {
        // 1. 获取所有候选配置类的全限定名
        List<String> configurations =
            getCandidateConfigurations(metadata, attributes);
        //    └→ 内部调用 SpringFactoriesLoader.loadFactoryNames()
        //       读取 META-INF/spring.factories 文件
        //       key = EnableAutoConfiguration.class.getName()

        // 2. 去重
        configurations = removeDuplicates(configurations);

        // 3. 排除用户 exclude 的类
        Set<String> exclusions = getExclusions(metadata, attributes);
        configurations.removeAll(exclusions);

        // 4. 排序(按 @AutoConfigureOrder / @AutoConfigureBefore / After)
        sort(configurations);

        return configurations.toArray(new String[0]);
    }
}

在 Spring Boot 2.x 中,配置类列表写在 META-INF/spring.factories 里:

META-INF/spring.factories(Spring Boot 2.x)
# Spring Boot 2.x 的注册方式
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
  org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
  org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
  # ... 共 130+ 个自动配置类
Spring Boot 3.x 的变化

从 Spring Boot 3.0 开始,spring.factories 机制被废弃,改用新文件 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,每行一个全限定类名,不再需要那个长长的 key。底层加载类也从 SpringFactoriesLoader 换成了 ImportCandidates。如果你维护的自定义 Starter 需要同时兼容 2.x 和 3.x,两个文件都要保留。

META-INF/spring/...AutoConfiguration.imports(Spring Boot 3.x)
# 新格式:每行一个全限定名,更简洁
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
加载链路一句话

@EnableAutoConfiguration → @Import(AutoConfigurationImportSelector) → 读取 META-INF 下的注册文件 → 拿到所有自动配置类的全限定名 → 去重、排除、排序 → 返回给 Spring 容器注册。

第 4 站

@Conditional 条件装配:按需激活的守门员

spring.factories 里注册了 130 多个自动配置类,但你的应用不可能全部需要。如果全部加载,不仅浪费内存,还会因为缺少依赖而报错。@Conditional 系列注解就是这些配置类的"守门员"——只有满足条件,配置类才会生效。

注解生效条件典型用法
@ConditionalOnClassclasspath 下存在指定类有 Tomcat 才装配 WebMvcAutoConfiguration
@ConditionalOnMissingClassclasspath 下不存在指定类用户未引入反应式框架时装配 Servlet 栈
@ConditionalOnBean容器中存在指定 Bean有 DataSource 才装配 JdbcTemplate
@ConditionalOnMissingBean容器中不存在指定 Bean用户没自定义时才装配默认实现
@ConditionalOnProperty配置属性满足条件spring.cache.type=redis 时才装配 Redis 缓存
@ConditionalOnWebApplication当前是 Web 应用只在 Web 环境下注册 DispatcherServlet
源码实例 — JacksonAutoConfiguration(简化)
@Configuration
@ConditionalOnClass(ObjectMapper.class)   // classpath 下有 Jackson 才生效
public class JacksonAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean             // 用户没自己定义 ObjectMapper 时才装配
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
        return builder.createXmlMapper(false).build();
    }
}

这里有一个面试常考的设计思想:@ConditionalOnMissingBean 体现了 Spring Boot 的"约定配置 + 用户覆盖"哲学。框架提供一个合理的默认实现,但用户一旦自定义了同类型 Bean,框架的默认实现就自动退让。

源码实例 — DataSourceAutoConfiguration(简化)
@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {

    @Configuration
    @ConditionalOnMissingBean(DataSource.class)  // 用户没配 DataSource 才走这里
    @Conditional(EmbeddedDatabaseCondition.class) // classpath 下有 H2/HSQL/Derby
    static class EmbeddedDatabaseConfiguration {
        @Bean
        public DataSource dataSource() {
            return new EmbeddedDatabaseBuilder()
                .setType(EmbeddedDatabaseType.H2).build();
        }
    }

    @Configuration
    @ConditionalOnProperty(prefix = "spring.datasource", name = "url")
    static class PooledDataSourceConfiguration {
        // 用户配了 spring.datasource.url 才走 HikariCP 连接池
    }
}

面试官追问:@Conditional 的底层原理是什么?

每个 @Conditional 注解都关联一个 Condition 实现类。Spring 在注册 Bean 之前,会调用 Condition.matches(ConditionContext, AnnotatedTypeMetadata) 方法,返回 true 才注册,返回 false 就跳过。例如 @ConditionalOnClass 对应的 OnClassCondition 内部通过 ClassLoader 检查指定类是否存在于 classpath。

条件装配的核心逻辑

130+ 自动配置类全部被加载到候选列表中,但最终是否注册到 Spring 容器,由 @Conditional 系列注解决定。条件不满足 → 整个配置类被跳过,就像它不存在一样。

第 5 站

自动装配全流程:从注解到 Bean 注册

前面几站拆解了各个环节,现在把它们串成一张完整的流程图。面试时能在脑中复现这张图,你就赢了。

@SpringBootApplication @SpringBootConfiguration = @Configuration @EnableAutoConfiguration 核心:开启自动装配 @ComponentScan 扫描当前包及子包 @Import(AutoConfigurationImportSelector.class) getCandidateConfigurations() 读取 META-INF/spring.factories(2.x) 或 META-INF/spring/...AutoConfiguration.imports(3.x) 去重 → 排除 exclude → 排序 @Conditional 条件检查通过? YES 注册到 Spring 容器 Bean 生效 NO 跳过 应用启动完成 Tomcat、DataSource、Jackson... 全部就绪 条件检查包含: @ConditionalOnClass / OnMissingBean / OnProperty / OnWebApplication ... 每个自动配置类独立判断,互不影响 图:Spring Boot 自动装配完整流程
图 1 Spring Boot 自动装配完整流程——从 @SpringBootApplication 到 Bean 注册
调试技巧:查看实际加载了哪些自动配置

application.properties 中加一行 debug=true,启动时会打印 Condition Evaluation Report,列出所有候选自动配置类及其匹配/不匹配原因。这是排查"为什么某个 Bean 没被注入"的利器。

第 6 站

自定义 Starter:从原理到实战

面试高频追问:"你自己写过 Starter 吗?写一个 Starter 需要几步?" 答案是 5 步。我们以一个"短信发送 Starter"为例,完整走一遍。

Starter 的本质是什么?

Starter 是一个空壳 Maven 模块,它本身不包含代码,只做一件事:引入"自动配置模块"的依赖。自动配置模块才是真正包含 @Configuration 类和 Bean 定义的地方。Spring 官方推荐将两者分开,便于用户按需排除。

第 1 步:创建自动配置模块 sms-spring-boot-autoconfigure

pom.xml — sms-spring-boot-autoconfigure
<!-- 自动配置模块的 pom -->
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-autoconfigure</artifactId>
    </dependency>
</dependencies>

第 2 步:定义配置属性类

SmsProperties.java — 绑定 application.yml 配置
@ConfigurationProperties(prefix = "sms")
public class SmsProperties {
    private String accessKey;       // sms.access-key
    private String secretKey;       // sms.secret-key
    private String signName = "默认签名"; // sms.sign-name

    // getter / setter 省略
}

第 3 步:编写自动配置类

SmsAutoConfiguration.java — 核心配置类
@Configuration
@ConditionalOnClass(SmsClient.class)     // classpath 下有 SmsClient 才生效
@ConditionalOnProperty(                   // 配置了 sms.access-key 才生效
    prefix = "sms",
    name = "access-key",
    matchIfMissing = false
)
@EnableConfigurationProperties(SmsProperties.class)
public class SmsAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean               // 用户没自定义时才装配默认实现
    public SmsClient smsClient(SmsProperties props) {
        return new DefaultSmsClient(
            props.getAccessKey(),
            props.getSecretKey(),
            props.getSignName()
        );
    }

    @Bean
    @ConditionalOnMissingBean
    public SmsTemplate smsTemplate(SmsClient client) {
        return new SmsTemplate(client);    // 封装发送短信的便捷方法
    }
}

第 4 步:注册自动配置类

META-INF/spring.factories(Boot 2.x)
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.example.sms.autoconfigure.SmsAutoConfiguration
META-INF/spring/...AutoConfiguration.imports(Boot 3.x)
com.example.sms.autoconfigure.SmsAutoConfiguration

第 5 步:创建 Starter 模块 sms-spring-boot-starter

pom.xml — sms-spring-boot-starter(空壳,只做依赖聚合)
<!-- Starter 模块:一个 Bean 都没有,只做依赖引入 -->
<dependencies>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>sms-spring-boot-autoconfigure</artifactId>
        <version>${project.version}</version>
    </dependency>
    <dependency>
        <groupId>com.example</groupId>
        <artifactId>sms-sdk</artifactId>
        <version>1.0.0</version>   <!-- 引入底层 SDK -->
    </dependency>
</dependencies>

使用方只需引入 Starter,在 application.yml 中配置属性即可:

使用方 application.yml
sms:
  access-key: AKID2024XXXX
  secret-key: SK2024YYYY
  sign-name:  "我的公司"
使用方代码——直接注入 SmsTemplate
@Service
public class OrderService {

    @Autowired
    private SmsTemplate smsTemplate;  // 自动注入,零配置

    public void notifyShipped(String phone, String orderNo) {
        smsTemplate.send(phone, "您的订单 " + orderNo + " 已发货");
    }
}
自定义 Starter 五步法

1. 创建 autoconfigure 模块(写 @Configuration + @Conditional) → 2. 定义 @ConfigurationProperties 绑定配置 → 3. 在 META-INF 注册自动配置类 → 4. 创建 starter 空壳模块聚合依赖 → 5. 使用方引入 starter + 写配置,开箱即用。

第 7 站

总结:关键注解速查 + Starter 清单

自动装配核心注解速查

注解作用面试关键词
@SpringBootApplication三合一组合注解@Configuration + @EnableAutoConfiguration + @ComponentScan
@EnableAutoConfiguration开启自动装配@Import(AutoConfigurationImportSelector)
AutoConfigurationImportSelector加载候选配置类读 META-INF → 去重 → 排除 → 排序
@ConditionalOnClassclasspath 有指定类才生效按需加载,避免缺依赖报错
@ConditionalOnMissingBean容器中没有指定 Bean 才装配默认实现 + 用户覆盖
@ConditionalOnProperty配置属性满足条件才生效功能开关、条件启用
@ConfigurationProperties绑定外部配置到 Java 对象类型安全的配置读取

自定义 Starter 五步清单

步骤模块关键动作
1autoconfigure编写 @Configuration 类,用 @Conditional 控制装配
2autoconfigure定义 @ConfigurationProperties 绑定用户配置
3autoconfigureMETA-INF 注册自动配置类(2.x 用 spring.factories,3.x 用 imports)
4starter创建空壳模块,聚合 autoconfigure + 底层 SDK 依赖
5使用方引入 starter 依赖 + 配置属性 = 开箱即用
面试加分项:debug=true

提到"在 application.properties 中设置 debug=true 可以查看 Condition Evaluation Report"会让面试官眼前一亮。这份报告会列出所有候选的自动配置类,标明 Positive matches(条件通过)和 Negative matches(条件未通过),以及每个条件不满足的具体原因——是排查自动装配问题的终极武器。

面试 30 秒总结模板

"Spring Boot 的自动装配基于 @EnableAutoConfiguration,它通过 @Import 导入 AutoConfigurationImportSelector,从 META-INF/spring.factories 或 imports 文件中加载所有候选自动配置类。每个配置类通过 @Conditional 系列注解(如 @ConditionalOnClass、@ConditionalOnMissingBean)进行条件判断,只有满足条件的配置类才会被注册到 Spring 容器。自定义 Starter 的核心就是:写一个 autoconfigure 模块定义条件和 Bean,再用一个 starter 空壳模块聚合依赖。"

自动装配公式(一句话记忆):

@EnableAutoConfiguration + META-INF 注册 + @Conditional 过滤 = 开箱即用