SpringBoot自动配置原理

前言

用过 SpringBoot 的同学都知道,相比于 SSM,SpringBoot 最大亮点就是自动配置 。 原生 Spring 开发:需要手动配置大量 XML、Bean、数据库连接池、视图解析器等,配置文件一大堆。 SpringBoot:引入 starter 依赖,少量配置甚至零配置,直接使用组件。

很多人只会使用,但经常被问:

  • SpringBoot 自动配置原理是什么?
  • @EnableAutoConfiguration作用?
  • starter 是什么?
  • 条件注解有哪些?什么时候生效?
  • SPI 机制在自动配置中扮演什么角色?

本文从底层源码角度,把整套自动配置流程讲清楚。

环境:SpringBoot 2.x

一、核心入口注解 @SpringBootApplication

打开 SpringBoot 启动类注解:

java 复制代码
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

@SpringBootApplication是复合注解,源码:

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
public @interface SpringBootApplication {}

由 3 个核心注解组成:

  1. @SpringBootConfiguration:本质就是@Configuration,代表当前类是配置类,可以注册 Bean
  2. @ComponentScan:组件扫描,默认扫描当前启动类所在包及其子包下所有@Component、@Service、@Controller等
  3. @EnableAutoConfiguration:自动配置核心开关(本篇重点)

二、@EnableAutoConfiguration 源码解析

java 复制代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

两个关键注解:

  1. @AutoConfigurationPackage:自动配置包,把启动类所在包注册进去,用于扫描包路径
  2. @Import(AutoConfigurationImportSelector.class):导入自动配置选择器,这是自动配置核心!

@Import是 Spring 原生注解,可以导入配置类、ImportSelector、ImportBeanDefinitionRegistrar。 AutoConfigurationImportSelector实现了ImportSelector接口,重写selectImports()方法。

selectImports () 方法作用

selectImports()返回全类名字符串数组,Spring 会把返回的所有类当成配置类,加载到容器。

AutoConfigurationImportSelector内部会:

  1. 读取META-INF/spring.factories文件
  2. 获取 key=org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的所有配置类全类名列表
  3. 过滤掉 exclude 排除的自动配置类
  4. 返回剩下的配置类名称数组交给 Spring 容器加载

SpringBoot SPI 机制 :spring.factories就是 SPI 配置文件。 路径:META-INF/spring.factories,在 spring-boot-autoconfigure 包里面。

spring.factories 示例片段:

复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.redis.RedisAutoConfiguration

每一行就是一个自动配置类。

三、自动配置类长什么样(以 DataSourceAutoConfiguration 举例)

打开DataSourceAutoConfiguration,里面大量条件注解。

java 复制代码
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceAutoConfiguration {
    // 内部创建DataSource Bean
}

条件注解(核心!决定 Bean 要不要创建)

注解 含义
@ConditionalOnClass classpath 下存在指定类,配置才生效
@ConditionalOnMissingBean 容器中不存在这个 Bean,才创建 Bean(用户自定义 Bean 优先)
@ConditionalOnProperty 配置文件存在指定配置项,并且匹配值
@ConditionalOnWebApplication 当前是 web 环境才生效
@ConditionalOnNotWebApplication 非 web 环境生效

核心规则:自动配置是 "兜底",如果我们自己定义了 Bean,SpringBoot 自动配置就不生效 。原理就是@ConditionalOnMissingBean。

@EnableConfigurationProperties

绑定配置文件application.yml/application.properties和属性类。 DataSourceProperties对应 spring.datasource 前缀配置,读取 yml 里面配置。

复制代码
spring:
  datasource:
    url: jdbc:mysql://xxx
    username: root

属性类@ConfigurationProperties(prefix = "spring.datasource")接收配置。

四、完整自动配置执行流程(一句话总结)

启动类打上 @SpringBootApplication,其中 @EnableAutoConfiguration 导入 AutoConfigurationImportSelector;该类通过 SPI 读取 META-INF/spring.factories,拿到所有自动配置类;每个自动配置类带有条件注解;Spring 容器启动时,根据条件注解判断是否实例化 Bean;满足条件就创建组件,不满足直接跳过。

详细分步:

  1. 执行SpringApplication.run(),启动 Spring 容器
  2. 解析启动类@SpringBootApplication注解
  3. @EnableAutoConfiguration导入AutoConfigurationImportSelector
  4. AutoConfigurationImportSelector读取spring.factories文件,加载候选自动配置类列表
  5. 过滤 exclude 排除的配置类
  6. Spring 依次解析每一个自动配置类
  7. 检查类上条件注解是否满足环境
  8. 条件满足:向容器注册 Bean;条件不满足:跳过该配置类
  9. 自动配置类里面的属性通过@ConfigurationProperties读取 yml 配置

五、什么是 starter?

starter = 依赖坐标 + 自动配置类

  • starter 本身几乎没有代码,只是 pom.xml,帮你引入需要的所有依赖;
  • 真正自动配置逻辑在spring-boot-autoconfigure包;

举例:spring-boot-starter-web

  • pom 引入 tomcat、springmvc 等依赖;
  • 自动配置:WebMvcAutoConfiguration,条件满足自动注册 DispatcherServlet、视图解析器等 Bean。

自定义 starter 思路:

  1. 创建模块,编写配置类 + 条件注解
  2. 编写META-INF/spring.factories,写入自动配置类全类名
  3. 打包,其他项目引入依赖即可自动配置

六、自动配置的优先级(覆盖自动配置 Bean)

SpringBoot 自动配置 Bean 优先级: 用户自定义 Bean > 自动配置 Bean 原因:自动配置使用@ConditionalOnMissingBean。只要你在配置类自己定义 DataSource Bean,SpringBoot 自动配置就不会创建。

示例:自定义数据源,覆盖自动配置

java 复制代码
@Configuration
public class MyDataSourceConfig{
    @Bean
    public DataSource dataSource(){
        // 自定义数据源
        return new HikariDataSource();
    }
}

容器存在 dataSource Bean,DataSourceAutoConfiguration里面的 Bean 不会实例化。

七、常见踩坑点

  1. 组件扫描范围问题 @ComponentScan默认扫描启动类所在包及子包。如果 Bean 放在启动类包之外,无法被扫描,需要手动指定scanBasePackages。
  2. spring.factories 位置写错,自定义 starter 自动配置不生效 必须放在resources/META-INF/spring.factories。
  3. 条件注解理解错误@ConditionalOnClass判断的是classpath 是否存在这个类,不是容器有没有 Bean。
  4. SpringBoot 2.7 之后,不再推荐 spring.factories 方式注册自动配置,改用META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件。

面试主要问 spring.factories 这套经典原理,2.7 + 新版本可以作为补充。

八、面试高频问答

Q1:SpringBoot 自动配置原理是什么?

A:核心是@EnableAutoConfiguration,通过AutoConfigurationImportSelector利用 SPI 读取spring.factories加载大量自动配置类;配置类带有条件注解,容器启动时根据当前环境判断是否注册 Bean;结合@ConfigurationProperties读取配置文件。
Q2:@EnableAutoConfiguration 和 @ComponentScan 区别?

A:@ComponentScan扫描开发者自己写的 Bean;@EnableAutoConfiguration加载 SpringBoot 内置的自动配置 Bean。
Q3:@ConditionalOnMissingBean 作用?

A:容器不存在指定 Bean 的时候才创建。目的:用户自定义 Bean 优先,SpringBoot 自动配置作为兜底。
Q4:starter 和 autoconfigure 包关系?

A:starter 是依赖聚合,方便引入 jar 包;autoconfigure 存放自动配置类的源码。
Q5:如何关闭某个自动配置?

A:启动类@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})排除指定自动配置类。
Q6:SpringBoot3.x 自动配置 SPI 有什么变化?

A:SpringBoot2.7 开始废弃 spring.factories 的自动配置方式,改用AutoConfiguration.imports文件,但是底层思想不变。

结语

SpringBoot 自动配置本质就是Spring 原生注解 + SPI 扩展机制 + 条件注解组合而成,不是什么黑魔法。 理解这套原理之后,不仅面试游刃有余,也可以自己动手开发自定义 starter。 很多开发只会使用 SpringBoot,但是看不懂底层,这也是区分初级后端和中级后端的一个考点。

相关推荐
海鸥-w1 小时前
数据库冗余字段设计笔记
java·数据库·笔记·oracle
工具派2 小时前
PDF签名贴上去为什么发虚?尺寸、DPI与坐标的边界实测
java·python·pdf
萧瑟余晖2 小时前
Spring Data综合实战与调优详解
java·后端·spring
yuniko-n2 小时前
【JUC】原子类 LongAdder
java
瑞got3 小时前
java使用CountDownLatch做并行查询
java·开发语言
用户574385305113 小时前
第 2 章:裸写 Query(原生驱动)
后端
用户574385305113 小时前
第 8 章:Service 层的诞生(硬编码版)
后端
用户574385305113 小时前
第 3 章:引入 Knex + Schema 自动加载
后端