面试必问 —— Spring Boot 是如何实现自动配置的?字详解
一、引言为什么自动配置是 Spring Boot 的灵魂如果你面试过 Java 后端岗位几乎一定会被问到这样一个问题“Spring Boot 是如何实现自动配置的”这个问题之所以高频是因为它直接考察了候选人对 Spring 核心机制的掌握程度以及是否有从“会用”到“懂原理”的进阶能力。在 Spring Boot 出现之前Java 开发者使用 Spring 框架搭建一个 Web 项目往往需要做大量重复、繁琐的配置工作。一个最普通的项目你可能需要配置数据源、事务管理器、消息监听容器、模板引擎、JSON 转换器、拦截器还要在 XML 或配置类中注册各种各样的 Bean。这些配置虽然灵活但重复度极高而且一旦某个依赖版本不匹配排查起来非常痛苦。Spring Boot 的核心设计理念之一就是“约定优于配置”。它在保留 Spring 强大能力的同时把大量常见场景的配置过程自动化让开发者只需要引入相应依赖、写少量配置应用就能直接跑起来。这背后依赖的正是自动配置机制。自动配置并不是魔法它本质上是 Spring 框架已有能力的一套组合拳注解元编程、Import 导入机制、SPI 加载机制、条件装配、Bean 定义注册等。把这些机制串起来再配合 Spring Boot 对大量第三方组件的默认配置就形成了我们感受到的“开箱即用”。本文将用超过 2 万字的篇幅从现象到原理、从源码到实战彻底拆解 Spring Boot 自动配置的实现机制。建议你带着下面几个问题阅读Spring Boot 启动时到底加载了哪些配置类这些配置类是从哪里来、如何被找到的为什么引入了 spring-boot-starter-data-redis 依赖RedisTemplate 就能直接注入为什么缺少某个类时应用仍然能正常启动而不会报 Bean 创建失败如果我想封装一个团队内部 Starter应该如何实现读完本文你不仅能从容应对这部分面试题还能真正理解 Spring Boot 的启动流程并在日常开发中更高效地排查问题、编写扩展。二、从一个对比实验开始传统 Spring 与 Spring Boot 的差异理解自动配置的价值最好的方式是对比传统 Spring 项目和 Spring Boot 项目在搭建同一个功能时的差异。下面以“引入一个基于 Redis 的缓存能力”为例进行说明。2.1 传统 Spring 项目的做法在传统 Spring 项目中要想使用 Redis通常需要完成以下步骤在 pom.xml 中引入 spring-data-redis 和 jedis 或 lettuce 依赖。在 XML 或 Java 配置类中创建 JedisConnectionFactory 或 LettuceConnectionFactory。创建 RedisTemplate 并注入 ConnectionFactory。配置序列化器比如把 Key 设置为 StringRedisSerializer把 Value 设置为 GenericJackson2JsonRedisSerializer。如果需要连接池还要配置 JedisPoolConfig 或 LettucePoolingClientConfiguration。如果使用多个 Redis 实例还要考虑主从、哨兵或 Cluster 模式的连接工厂。这些配置不仅写起来繁琐而且每个项目都要重写一遍类似代码。更麻烦的是不同团队对 RedisTemplate 的序列化约定不一致导致缓存数据格式混乱。2.2 Spring Boot 项目的做法在 Spring Boot 项目中上述步骤被压缩成了两步在 pom.xml 中引入 spring-boot-starter-data-redis。在 application.yml 中填写连接信息比如 host、port、password。spring: redis: host: localhost port: 6379 password: timeout: 3s启动应用后容器中就直接拥有了 RedisConnectionFactory、RedisTemplate、StringRedisTemplate 等 Bean可以直接注入使用。这就是自动配置的直观效果。那么 Spring Boot 是怎么在背后把这些 Bean 注册到容器中的呢下面开始逐步拆解。三、自动配置的入口SpringBootApplication 注解解析每一个 Spring Boot 应用的启动类上基本都会标注 SpringBootApplication。它是自动配置的“总入口”也是我们理解自动配置的起点。先来看它的定义Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Documented Inherited SpringBootConfiguration EnableAutoConfiguration ComponentScan(excludeFilters { Filter(type FilterType.CUSTOM, classes TypeExcludeFilter.class), Filter(type FilterType.CUSTOM, classes AutoConfigurationExcludeFilter.class) }) public interface SpringBootApplication { ... }可以看到SpringBootApplication 本身并不承担具体逻辑它是一个组合注解核心由三个注解构成SpringBootConfiguration标记当前类是配置类。EnableAutoConfiguration开启自动配置。ComponentScan开启组件扫描。其中与自动配置直接相关的是 EnableAutoConfiguration接下来重点分析它。另外两个注解虽然不直接参与自动配置类加载但理解它们的职责有助于建立完整的启动视图。3.1 SpringBootConfigurationSpringBootConfiguration 的定义如下Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Documented Configuration public interface SpringBootConfiguration { }它本质上就是 Configuration 的别名。也就是说启动类本身会被当作一个配置类来处理。Configuration 类在 Spring 容器启动时会由 ConfigurationClassPostProcessor 进行处理它内部的方法如果标注了 Bean会被注册为 Bean 定义并纳入容器管理。这意味着启动类除了作为程序入口还可以通过 Bean 方法注册一些自定义 Bean。不过在实际项目中更常见的做法是把配置拆分到独立的 Configuration 类中启动类只负责“点火”。3.2 ComponentScanComponentScan 用于指定需要扫描的包路径。Spring Boot 启动类默认会扫描其所在包及其子包下的 Component、Service、Repository、Controller 等注解标注的类。在 SpringBootApplication 中ComponentScan 还通过 excludeFilters 排除了两类过滤器TypeExcludeFilterSpring Boot 提供的一个扩展点允许通过 getExcludeFilters 机制在测试或特定环境下排除某些 Bean。AutoConfigurationExcludeFilter用于在组件扫描时排除自动配置类。这个过滤器会判断一个配置类是否既是 Configuration 又标有 AutoConfiguration 或者被自动配置机制管理如果是则不在普通组件扫描阶段加载而是在后续自动配置阶段统一处理避免重复注册。这个设计体现了 Spring Boot 对加载顺序和职责边界的精细控制普通业务 Bean 走组件扫描自动配置 Bean 走专门的自动配置流程。3.3 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 {}; }这个注解有三个关键点AutoConfigurationPackage负责把启动类所在包记录下来供后续实体扫描等机制使用。Import(AutoConfigurationImportSelector.class)导入自动配置导入选择器这是自动配置类的核心加载入口。exclude 与 excludeName 属性允许开发者排除某些不需要的自动配置类。此外ENABLED_OVERRIDE_PROPERTY 常量对应 spring.boot.enableautoconfiguration 属性可以在配置中通过该属性快速开启或关闭自动配置。默认情况下自动配置是开启的。四、深入 EnableAutoConfiguration 的两个核心注解4.1 AutoConfigurationPackageAutoConfigurationPackage 的定义非常简短Target(ElementType.TYPE) Retention(RetentionPolicy.RUNTIME) Documented Inherited Import(AutoConfigurationPackages.Registrar.class) public interface AutoConfigurationPackage { }它通过 Import 导入了一个 Registrar这个类实现了 ImportBeanDefinitionRegistrar 接口。在 Spring 处理配置类的过程中会回调该 Registrar从而把启动类所在包注册到容器中供后续需要扫描包路径的机制使用。AutoConfigurationPackages 内部维护了一个 PackageImports 列表。它的作用之一就是为 Spring Data JPA 的 EnableJpaRepositories 等机制提供默认的实体扫描包。也就是说当你在项目中使用 JPA 时Spring Boot 知道应该去哪个包扫描你的 Entity 实体类。AutoConfigurationPackage 并不直接负责“加载哪些自动配置类”这是很多初学者的误解。真正完成自动配置类导入的是下面的 Import(AutoConfigurationImportSelector.class)。4.2 Import 机制回顾在继续深入之前有必要回顾一下 Spring 的 Import 机制它是理解自动配置的地基。Import 可以导入以下几种类型的类普通 Configuration 配置类把该配置类也纳入配置类处理流程其中的 Bean 方法会被注册。ImportSelector 的实现类Spring 调用其 selectImports 方法该方法返回一组需要导入的类全限定名。ImportBeanDefinitionRegistrar 的实现类直接向容器注册 BeanDefinition可以拿到 BeanDefinitionRegistry 做更细粒度的控制。DeferredImportSelector 的实现类这是 ImportSelector 的增强版本允许延迟导入并且可以指定导入的分组和顺序。AutoConfigurationImportSelector 最终实现的就是 DeferredImportSelector 接口。为什么偏偏要用延迟导入因为自动配置类往往需要依赖其他普通配置类处理完毕后再加载这样才能保证条件评估时所有用户自定义 Bean 都已经注册ConditionalOnMissingBean 等判断才能生效。如果自动配置在用户配置之前执行就很容易出现同类型 Bean 冲突或条件误判。五、自动配置类的加载机制从 spring.factories 到 AutoConfiguration.imports既然 EnableAutoConfiguration 通过 Import 导入了 AutoConfigurationImportSelector那么下一个核心问题就是AutoConfigurationImportSelector 是从哪里拿到那一长串自动配置类的在早期的 Spring Boot 版本中答案是一个我们非常熟悉的文件META-INF/spring.factories。Spring Boot 以它作为 SPI 配置文件把自动配置类全限定名列表写在里面。# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration\ org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\ org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\ ...Spring Boot 在运行时通过 SpringFactoriesLoader 加载该文件读取 EnableAutoConfiguration 键对应的值得到一组自动配置类全限定名再把这些类实例化并注册到容器。这是自动配置的经典实现方式。从 Spring Boot 2.7 开始官方对自动配置的注册方式进行了调整。spring.factories 仍然被保留用于其他类型的 SPI 注册但自动配置类逐步迁移到了专门的文件META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports这个新文件每行一个自动配置类全限定名不再使用 properties 的键值对格式语义更清晰也避免了一边加载配置一边解析大量无关键的问题。org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration org.springframework.boot.autoconfigure.aop.AopAutoConfiguration org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration ...在 Spring Boot 2.7 至 2.x 的过渡版本中两种文件可能同时存在并兼容读取到了 Spring Boot 3.x自动配置类已完全使用 AutoConfiguration.imports 文件加载。无论底层文件如何变化整体设计思路是一致的通过约定的资源路径集中声明可被自动配置的候选类再交由条件评估决定是否真正生效。SpringFactoriesLoader 在自动配置流程中主要提供两类方法loadFactories加载并实例化指定类型的工厂实现类。loadFactoryNames只加载指定类型的工厂实现类名称不实例化。自动配置类加载使用的是后者的语义即先拿到类名列表交给排序、过滤、条件评估等流程处理最后才实例化。六、条件装配Conditional 与 ConditionalOnXxx 注解族拿到候选自动配置类列表之后Spring Boot 并不是把这些类全部无条件注册。如果那样做引入一个 Redis Starter 后容器中可能连不存在的消息中间件 Bean 都试图创建应用根本没法启动。真正让自动配置“智能”起来的是 Spring 的条件装配能力。它的基础是 Conditional 注解和 Condition 接口。6.1 Conditional 与 Condition 接口Conditional 是 Spring 4.0 引入的注解通常标注在 Configuration 类或 Bean 方法上。它的 value 是一个或多个 Condition 实现类的 Class 对象。只有当所有 Condition 的 matches 方法都返回 true 时对应的配置类或 Bean 才会被注册。FunctionalInterface public interface Condition { boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }ConditionContext 提供了对环境信息的访问包括 BeanDefinitionRegistry、ConfigurableListableBeanFactory、Environment、ResourceLoader、ClassLoader 等。AnnotatedTypeMetadata 提供了对被标注位置的元数据信息包括类上、方法上的注解及其属性值。例如判断容器中是否存在某个 Bean或者类路径中是否存在某个类都可以在 matches 方法中完成。6.2 Spring Boot 的常用条件注解Spring Boot 在 Conditional 基础上进行了封装提供了一批更贴近业务表达的条件注解统一放在 org.springframework.boot.autoconfigure.condition 包下。最常用的有注解生效条件ConditionalOnClass类路径中存在指定类时生效ConditionalOnMissingClass类路径中不存在指定类时生效ConditionalOnBean容器中存在指定 Bean 时生效ConditionalOnMissingBean容器中不存在指定 Bean 时生效ConditionalOnProperty指定配置属性满足条件时生效ConditionalOnWebApplication当前是 Web 应用时生效ConditionalOnNotWebApplication当前不是 Web 应用时生效ConditionalOnExpressionSpEL 表达式求值为 true 时生效ConditionalOnJavaJava 版本满足范围时生效ConditionalOnResource存在指定资源时生效ConditionalOnSingleCandidate指定类型只有一个候选 Bean 或存在首选 Bean 时生效这些注解覆盖了自动配置判断的绝大多数场景。下面选取几个最典型的进行源码级分析。6.3 ConditionalOnClass / ConditionalOnMissingClass在 Spring Boot 自动配置类中ConditionalOnClass 和 ConditionalOnMissingClass 几乎是出现频率最高的一组条件注解。它们的源码定义大致如下Target({ ElementType.TYPE, ElementType.METHOD }) Retention(RetentionPolicy.RUNTIME) Documented Conditional(OnClassCondition.class) public interface ConditionalOnClass { Class?[] value() default {}; String[] name() default {}; } Target({ ElementType.TYPE, ElementType.METHOD }) Retention(RetentionPolicy.RUNTIME) Documented Conditional(OnClassCondition.class) public interface ConditionalOnMissingClass { String[] value() default {}; }可以看到两个注解最终都通过 Conditional 指向了同一个 Condition 实现类org.springframework.boot.autoconfigure.condition.OnClassCondition。区别在于ConditionalOnClass 表示“类路径中存在指定类时成立”ConditionalOnMissingClass 表示“类路径中不存在指定类时成立”。ConditionalOnClass 同时提供 value 和 name 两个属性。value 接收 Class 对象写法更直观但有一个潜在问题如果某个类不在当前 classpath 中直接写在注解里会导致编译阶段解析失败。因此 Spring Boot 推荐在判断第三方可选依赖时使用 name 属性用字符串写类的全限定名而 value 更适合像 javax.servlet.Servlet 这种已经纳入依赖管理的 API 类。ConditionalOnMissingClass 则只提供字符串类型的 value正好印证了这一设计意图。OnClassCondition 继承自 SpringBootCondition在 matches 方法中会读取注解中的类名信息再结合当前类加载器进行类存在性判断。它的判断并不是简单调用 Class.forName而是批量收集可解析的类名和需要判断的类名统一交给 ClassNameFilter 处理从而避免在大量条件评估时频繁触发类加载异常。以 JdbcTemplateAutoConfiguration 为例它通常会在类上标注 ConditionalOnClass(JdbcTemplate.class)。只有项目的 classpath 中确实存在 org.springframework.jdbc.core.JdbcTemplate 时这组 JDBC 自动化配置才会继续评估后续的 ConditionalOnProperty、ConditionalOnMissingBean 等条件否则整组配置都会被直接跳过。七、实战封装一个团队内部 Spring Boot Starter前文已经完整梳理了自动配置从入口、导入、加载到条件装配的全链路。接下来把理论落到工程实践通过封装一个团队内部消息客户端的 Starter说明如何复用 Spring Boot 的自动配置机制。7.1 明确 Starter 的模块边界在设计 Starter 时通常建议将其拆成两个模块autoconfigure 模块负责自动配置逻辑starter 模块只负责聚合依赖。这样可以让使用方按需引入自动配置模块也能避免 starter 中携带过多传递依赖。假设我们要封装一个团队内部的 NoticeClient 通知发送组件希望业务方只需要引入 Starter并在 application.yml 中配置 endpoint 和 apiKey就可以直接注入 NoticeClient 使用。7.2 编写配置属性类与自动配置类首先在 autoconfigure 模块中新增配置属性类和自动配置类ConfigurationProperties(prefix notice.client) public class NoticeClientProperties { private String endpoint http://localhost:8080/notice; private String apiKey; private int connectTimeout 3000; private int readTimeout 5000; // 省略 getter/setter }AutoConfiguration EnableConfigurationProperties(NoticeClientProperties.class) ConditionalOnClass(NoticeClient.class) ConditionalOnProperty(prefix notice.client, name enabled, havingValue true, matchIfMissing true) public class NoticeClientAutoConfiguration { Bean ConditionalOnMissingBean public NoticeClient noticeClient(NoticeClientProperties properties) { return new NoticeClientBuilder() .endpoint(properties.getEndpoint()) .apiKey(properties.getApiKey()) .connectTimeout(properties.getConnectTimeout()) .readTimeout(properties.getReadTimeout()) .build(); } }这里使用 AutoConfiguration 标记自动配置类用 ConditionalOnClass 保证只有业务 JAR 存在时才评估配置用 ConditionalOnProperty 提供 enabled 开关用 ConditionalOnMissingBean 支持使用者覆盖默认 Bean。7.3 注册自动配置类Spring Boot 2.7 之后的项目应使用 AutoConfiguration.imports 文件注册自动配置类。在 autoconfigure 模块的资源目录创建META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件内容为一行一个类全限定名com.example.notice.autoconfigure.NoticeClientAutoConfiguration如果团队还需要兼容 Spring Boot 2.6 及更早版本可以同时在 META-INF/spring.factories 中写入 EnableAutoConfiguration 键对应的类名并建议通过构建脚本或测试保证两份清单不会漏同步。7.4 在业务项目中接入业务方只需要做两件事。第一在 pom.xml 中引入 Starterdependency groupIdcom.example.notice/groupId artifactIdnotice-client-spring-boot-starter/artifactId version1.0.0/version /dependency第二在 application.yml 中填写连接信息notice: client: enabled: true endpoint: https://notice.example.com api-key: ${NOTICE_API_KEY} connect-timeout: 3000 read-timeout: 5000启动应用后容器中就会存在 NoticeClient Bean业务代码可以直接通过 Autowired 或构造器注入使用。当使用者想替换实现时只需要自己声明一个 NoticeClient Bean由于 ConditionalOnMissingBean 的存在自动配置会主动退让。八、总结把自动配置的链路串起来到这里我们可以把 Spring Boot 自动配置的核心链路做一次完整回顾启动类标注 SpringBootApplication它是 SpringBootConfiguration、EnableAutoConfiguration 和 ComponentScan 的组合注解。EnableAutoConfiguration 通过 Import 导入 AutoConfigurationImportSelector并借助 AutoConfigurationPackage 记录启动类所在包。Spring 在配置类处理阶段调用 AutoConfigurationImportSelector它作为 DeferredImportSelector 延迟执行从而保证用户自定义配置先注册。Selector 从 AutoConfiguration.imports 文件读取候选自动配置类全限定名然后进行排序、去重、排除和条件过滤。每一组自动配置类或 Bean 方法都会经历条件装配检查例如判断类是否存在、Bean 是否已存在、配置属性是否满足要求。通过条件评估的配置类最终被解析为 BeanDefinition注册到容器中形成开发者可以直接注入的 RedisTemplate、DataSource、JdbcTemplate 等 Bean。理解这条链路之后再回答“为什么引入 Starter 就能直接注入”“为什么少某个类应用还能正常启动”这些问题答案就不再神秘。自动配置的本质是 Spring 注解元编程、导入机制、SPI 加载和条件装配能力的组合运用。最后留两个延伸思考题帮助你把知识进一步内化如果同一个类型同时存在用户自定义 Bean 和自动配置 BeanSpring Boot 是按什么顺序保证用户 Bean 优先如果希望自己的自动配置类在某个第三方自动配置之后执行应该通过什么机制控制顺序前者和条件评估的注册时机有关后者则可以关注 AutoConfigureOrder、AutoConfigureBefore 和 AutoConfigureAfter 注解。沿着这两个方向继续阅读源码你会对自动配置有更立体的认识。