第四篇:SpringBoot自动配置——约定大于配置的底层原理

前言

在前三篇文章中,我们拆解了Spring的IoC容器、AOP机制和SpringMVC的请求处理流程。但如果你用传统Spring开发过项目,你一定记得被XML配置支配的恐惧------DataSource要配、ViewResolver要配、Jackson转换器要配,光是配置文件就几百行。

SpringBoot改变了这一切。引入一个starter依赖,自动就能用了。面试中,自动配置是SpringBoot最核心的考点:

"@SpringBootApplication背后做了什么?"

"自动配置的原理是什么?spring.factories文件是干什么的?"

"如何自定义一个starter?"

"条件注解@ConditionalOnClass、@ConditionalOnMissingBean分别解决什么场景?"

本文拆解SpringBoot自动配置的底层原理,从启动注解一路讲到自定义starter。

本文核心问题:

  1. SpringBoot和传统Spring有什么区别?"自动配置"到底自动了什么?
  2. @SpringBootApplication背后做了什么?三个核心注解各自负责什么?
  3. @EnableAutoConfiguration是如何触发自动配置的?spring.factories文件是什么?
  4. 条件注解@ConditionalOnClass、@ConditionalOnMissingBean分别解决什么场景?它们是如何工作的?
  5. 自动配置类的执行顺序是怎样的?@AutoConfigureBefore和@AutoConfigureAfter控制什么?
  6. 如何自定义一个starter?需要哪些步骤?
  7. SpringBoot是如何自动配置SpringMVC的?为什么引入Jackson依赖就能处理JSON?
  8. 自动配置和按需配置的边界在哪?

读完本文,你将对SpringBoot自动配置拥有从启动原理到自定义starter的完整理解。


一、传统Spring vs SpringBoot

疑问:SpringBoot和传统Spring有什么区别?"自动配置"到底自动了什么?

回答:传统Spring需要开发者手动配置几乎一切。SpringBoot根据classpath中的依赖自动推测开发者意图,自动配置好所需的Bean。

1.1 传统Spring的配置

xml 复制代码
<!-- 传统Spring MVC项目需要的配置(部分) -->

<!-- 1. 配置DispatcherServlet -->
<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>

<!-- 2. 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/"/>
    <property name="suffix" value=".jsp"/>
</bean>

<!-- 3. 配置Jackson消息转换器 -->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
        </list>
    </property>
</bean>

<!-- 4. 配置数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="url" value="jdbc:mysql://localhost:3306/test"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</bean>

<!-- 5. 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

1.2 SpringBoot的配置

java 复制代码
// 一个@SpringBootApplication替代了所有XML配置
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

// application.yml只需要关注业务配置
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: 123456

传统Spring你需要显式注册每一个Bean、配置每一项能力。SpringBoot只要你引入starter依赖,它通过classpath自动感知并配置好所需的Bean。 你的工作从写XML配置变成了在application.yml中声明业务参数。


二、@SpringBootApplication的三个核心注解

疑问:@SpringBootApplication只是一个注解,它怎么做到替代一堆XML配置的?

回答:@SpringBootApplication本身是一个组合注解,它合并了三个核心注解------@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan。三者协同,完成了Bean注册、自动配置和组件扫描三件事。

java 复制代码
@SpringBootConfiguration    // ← 本质是@Configuration,声明这是配置类
@EnableAutoConfiguration    // ← 触发自动配置,核心中的核心
@ComponentScan(             // ← 扫描当前包及子包下的@Controller、@Service、@Repository等
    excludeFilters = { @Filter(type = FilterType.CUSTOM, 
                       classes = TypeExcludeFilter.class) }
)
public @interface SpringBootApplication {
}

三个核心注解的分工

注解 作用 对应传统Spring的什么
@SpringBootConfiguration 标记当前类是配置类,本质是@Configuration <context:annotation-config/>
@EnableAutoConfiguration 根据classpath自动配置Bean 替代开发者手动写的一大堆@Bean<bean>
@ComponentScan 扫描当前包下的组件(@Controller、@Service、@Repository等) <context:component-scan>

@EnableAutoConfiguration是三者的核心------它完成了从"手动配置一切"到"自动感知、自动配置"的关键进化。


三、@EnableAutoConfiguration的底层原理

疑问:@EnableAutoConfiguration具体是怎么触发自动配置的?它怎么知道该配置什么?

回答:@EnableAutoConfiguration通过@Import注解导入AutoConfigurationImportSelector。这个selector读取spring.factories文件中声明的所有自动配置类,然后根据条件注解判断哪些配置类应该生效。

3.1 @EnableAutoConfiguration源码

java 复制代码
@Import(AutoConfigurationImportSelector.class)  // ← 核心:导入自动配置选择器
public @interface EnableAutoConfiguration {
}

@Import是Spring的一个扩展点,它的作用是将指定的类注册为Bean。通常@Import导入的是一个具体的类,但这里导入的是AutoConfigurationImportSelector------一个"选择器",它不直接注册自己,而是计算出一组需要被注册的类。

3.2 AutoConfigurationImportSelector做了什么

java 复制代码
// AutoConfigurationImportSelector的核心逻辑(简化)
public class AutoConfigurationImportSelector 
        implements DeferredImportSelector, BeanClassLoaderAware, ... {
    
    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        // 1. 读取META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
        //    文件中声明的所有自动配置类
        List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
        
        // 2. 根据条件注解进行过滤
        configurations = filter(configurations, autoConfigurationMetadata);
        
        // 3. 返回最终需要加载的自动配置类列表
        return configurations.toArray(new String[0]);
    }
}

这个选择器的工作分三步:读取候选配置类→根据条件注解过滤→返回最终需要注册的Bean类。 它本身不配置任何东西,它只是一张"推荐清单",由后面的条件注解决定每一条是否实际生效。

3.3 spring.factories文件

在SpringBoot 2.x中,自动配置类通过META-INF/spring.factories文件声明。SpringBoot 3.x改为META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,每行一个配置类。

SpringBoot核心包中的自动配置类列表(部分):

properties 复制代码
# spring-boot-autoconfigure.jar 中的 spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
...

这就是自动配置的核心所在:SpringBoot启动时,读取所有候选配置类,然后逐一检查条件注解,决定哪些配置类应该生效。


四、条件注解------按需启用配置的过滤器

疑问:自动配置类有上百个,不可能全部启用。SpringBoot怎么判断哪个该生效?

回答:通过条件注解。每个自动配置类上都标注了@ConditionalOnXxx,Spring在加载配置类时检查这些条件------满足条件的才注册其中的Bean,不满足的直接跳过。

4.1 常用条件注解

条件注解 含义 典型场景
@ConditionalOnClass classpath中有指定类时生效 引入Jackson依赖→自动配置JSON转换器
@ConditionalOnMissingClass classpath中没有指定类时生效 没有引入特定依赖时退而求其次用替代方案
@ConditionalOnBean 容器中有指定Bean时生效 等数据源Bean注册完毕后才配置事务管理器
@ConditionalOnMissingBean 容器中没有指定Bean时生效 用户没有自定义ObjectMapper→用默认的
@ConditionalOnProperty 配置文件中有指定属性时生效 通过spring.redis.enabled=false关闭Redis
@ConditionalOnExpression SpEL表达式结果为true时生效 复杂的条件组合判断
@ConditionalOnJava JDK版本满足条件时生效 不同JDK版本用不同实现

4.2 一个实际的例子------RedisAutoConfiguration

java 复制代码
// spring-boot-autoconfigure 中的 Redis 自动配置类
@AutoConfiguration                                          // 标记这是自动配置类
@ConditionalOnClass({RedisOperations.class})                // ← classpath中有RedisOperations才生效
@EnableConfigurationProperties(RedisProperties.class)       // 启用配置属性绑定
@Import({LettuceConnectionConfiguration.class,              // 引入连接配置
         JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")       // ← 用户没有自定义redisTemplate才创建
    public RedisTemplate<Object, Object> redisTemplate(
            RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        return template;
    }
    
    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory connectionFactory) {
        return new StringRedisTemplate(connectionFactory);
    }
}

这段代码完美展示了自动配置的"按需生效"原则

  • @ConditionalOnClass:只有引入Redis依赖时才生效。如果classpath中没有RedisOperations,整个配置类都不会被加载
  • @ConditionalOnMissingBean:用户如果自己定义了redisTemplateBean,SpringBoot就不创建默认的了。这是"约定大于配置"的体现------用户可以选择覆盖默认配置,不需要时直接用默认的,需要时自定义即可

4.3 一个配置类的完整生效链路

复制代码
SpringBoot启动
  → 读取spring.factories → 获得候选配置类列表
  → 遍历每个配置类,检查其上的条件注解
      → RedisAutoConfiguration
          ├── 检查 @ConditionalOnClass(RedisOperations.class)
          │       ├── classpath中有 → 继续
          │       └── classpath中没有 → 跳过这个配置类
          └── 生效 → 解析其中的@Bean方法
              └── 创建redisTemplate
                  └── 检查 @ConditionalOnMissingBean
                      ├── 容器中没有同名Bean → 创建
                      └── 容器中已有 → 跳过

五、配置执行顺序------@AutoConfigureBefore和@AutoConfigureAfter

疑问:多个自动配置类之间有依赖关系怎么办?比如WebMvc的自动配置需要在DispatcherServlet注册之后才执行?

回答:通过@AutoConfigureBefore和@AutoConfigureAfter声明配置类之间的顺序依赖。

java 复制代码
// WebMvcAutoConfiguration声明它必须在几个配置类之后执行
@AutoConfigureAfter(
    DispatcherServletAutoConfiguration.class,   // DispatcherServlet注册完成后
    TaskExecutionAutoConfiguration.class,
    ValidationAutoConfiguration.class
)
public class WebMvcAutoConfiguration {
    // 配置HandlerMapping、HandlerAdapter、消息转换器等
}

如果一个配置类的Bean依赖另一个配置类创建的Bean,需要用@AutoConfigureAfter显式声明顺序。否则可能因依赖的Bean还未创建而导致配置失败。


六、如何自定义一个starter?

疑问:项目中想把自定义的公共功能做成starter,让其他服务引入依赖就能自动生效。怎么做?

回答:一个自定义starter分为三个模块------自动配置模块、starter模块、和业务属性类。核心是把@Configuration类注册到spring.factories中,配合条件注解按需启用。

6.1 以秒杀系统的库存扣减工具为例

复制代码
my-starter/
├── my-spring-boot-starter/                    # starter模块(空JAR,只放pom.xml)
│   └── pom.xml                                # 依赖my-spring-boot-autoconfigure
│
├── my-spring-boot-autoconfigure/              # 自动配置模块
│   ├── pom.xml
│   └── src/main/java/
│       ├── MyServiceAutoConfiguration.java    # 自动配置类
│       └── MyProperties.java                  # 配置属性类
│
└── my-core/                                   # 核心功能模块
    └── src/main/java/
        └── MyService.java                     # 实际业务逻辑

6.2 自动配置类

java 复制代码
@AutoConfiguration
@EnableConfigurationProperties(MyProperties.class)   // 绑定application.yml中的属性
@ConditionalOnClass(MyService.class)                 // 核心类在classpath中时才生效
public class MyServiceAutoConfiguration {
    
    @Bean
    @ConditionalOnMissingBean                       // 用户可自定义覆盖
    public MyService myService(MyProperties properties) {
        return new MyService(properties.getVersion(), properties.getTimeout());
    }
}

6.3 配置属性类

java 复制代码
@ConfigurationProperties(prefix = "my.service")
public class MyProperties {
    private String version = "1.0";        // 默认值
    private int timeout = 3000;            // 默认超时3秒
    // getter/setter
}

6.4 注册配置类

META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中添加:

复制代码
com.example.autoconfigure.MyServiceAutoConfiguration

6.5 使用方

xml 复制代码
<!-- 其他服务引入starter -->
<dependency>
    <groupId>com.example</groupId>
    <artifactId>my-spring-boot-starter</artifactId>
    <version>1.0.0</version>
</dependency>
yaml 复制代码
# application.yml:自动绑定到MyProperties
my:
  service:
    version: "2.0"
    timeout: 5000

starter引入后,自动配置类被注册,条件注解检查classpath→配置Bean→绑定application.yml属性→Bean创建完成,业务代码中直接注入MyService使用。开发者不需要手动写任何配置。


七、面试中这样回答

面试官:"SpringBoot的自动配置原理是什么?"

回答框架

"SpringBoot通过@EnableAutoConfiguration注解触发自动配置,它内部通过@Import导入了AutoConfigurationImportSelector。这个Selector启动时从spring.factories文件中读取所有候选的自动配置类列表,然后遍历每个配置类,检查其上标注的条件注解------比如@ConditionalOnClass判断classpath中是否有对应的依赖,@ConditionalOnMissingBean判断用户是否已经自定义了这个Bean。满足条件的配置类生效,不满足的跳过。这就是为什么引入starter依赖就能自动配置好一切。"

面试官:"如何自定义一个starter?"

回答

"分为三步。第一步写自动配置类,标注@AutoConfiguration,加上@ConditionalOnClass等条件注解,在类中定义需要自动创建的Bean,用@ConditionalOnMissingBean允许用户覆盖。第二步在spring.factories文件中注册这个自动配置类,让SpringBoot能扫描到它。第三步把自动配置模块和starter依赖打包发布------stater模块是空JAR,自动配置模块包含配置类和属性类。其他服务引入stater后,自动注册Bean并按需绑定application.yml中的配置属性。"


总结

  • @SpringBootApplication由三个核心注解组成:@SpringBootConfiguration(声明配置类)、@EnableAutoConfiguration(触发自动配置)、@ComponentScan(扫描组件)
  • @EnableAutoConfiguration通过AutoConfigurationImportSelector扫描spring.factories中声明的自动配置类,这是整个自动配置机制的入口
  • 条件注解是"按需生效"的过滤器:@ConditionalOnClass(有依赖才配)、@ConditionalOnMissingBean(用户可覆盖)是最常用的两个。条件注解让自动配置"该来的来,不该来的不来"
  • @AutoConfigureBefore/After控制配置顺序,解决配置类之间的Bean依赖关系
  • 自定义starter的三个要素:自动配置类 + spring.factories注册 + @ConditionalOnMissingBean允许覆盖
  • SpringBoot自动配置的本质:根据classpath中的依赖推测开发者的意图,自动注册所需Bean,同时保留用户覆盖的能力。约定大于配置,但不强制

下一篇预告:Spring原理(五)------Spring事务管理:@Transactional的底层实现与失效场景。拆解事务拦截器如何通过AOP实现、七种事务传播行为的适用场景、以及@Transactional在什么情况下会失效------自调用、非public方法、异常类型不匹配等。

相关推荐
不知名的忻1 小时前
Dijkstra算法(朴素版&堆优化版)
java·数据结构·算法··dijkstra算法
苏三说技术1 小时前
美团二面:高并发下如何保证接口幂等性?
java·数据库
追逐时光者1 小时前
C#/.NET/.NET Core技术前沿周刊 | 第 70 期(2026年5.01-5.10)
后端·.net
yaoxin5211231 小时前
402. Java 文件操作基础 - 读取二进制文件
java·开发语言·python
沐浴露z1 小时前
面试官:静态变量与非静态成员变量的区别?别再死记硬背了!
java·jvm
极创信息1 小时前
信创软件快速适配信创改造,实战落地思路
java·大数据·数据库·人工智能·mvc·软件工程·hibernate
摇滚侠2 小时前
Java 项目教程《尚庭公寓》标签管理、自定义 converter 14 - 18
java·elasticsearch·架构
程序员清风2 小时前
科普一下:大模型Token的收费逻辑!
java·后端·面试
Nyarlathotep01132 小时前
并发集合类(4):ArrayBlockingQueue
java·后端