Spring Boot 的"约定优于配置"由三条主线支撑:SpringApplication 对启动过程的编排、IoC 容器的 Bean 加载、基于条件注解的自动配置。本文以 Spring Boot 4.x(底层为 Spring Framework 7)源码为基准,沿启动顺序把这三条主线讲清楚。4.x 最大的工程变化是模块化拆分,启动主线本身保持不变。Bean 的实际使用规范见Spring Boot Bean 使用规范

启动原理

@SpringBootApplication 拆解

1
2
3
4
5
6
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

@SpringBootApplication 是复合注解,三个组成部分各司其职:

1
2
3
4
5
6
7
8
9
@SpringBootConfiguration   // 本质是 @Configuration
@EnableAutoConfiguration // 开启自动装配
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM,
classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM,
classes = AutoConfigurationExcludeFilter.class)
})
public @interface SpringBootApplication {}
  • @SpringBootConfiguration:标记主类为配置类
  • @EnableAutoConfiguration:自动配置的入口
  • @ComponentScan:扫描主类所在包及子包,两个 excludeFilter 防止自动配置类被当作普通组件重复注册

构造阶段:new SpringApplication()

SpringApplication.run() 分两步:先构造,再运行。构造阶段只做三件事:

  1. 推断应用类型(WebApplicationType.deduce(),3.x 中叫 deduceFromClasspath()):模块化之后由 spring-boot-webmvc、spring-boot-webflux 各自注册的 Deducer 策略判定——存在 MVC 为 Servlet,仅有 WebFlux 为 Reactive,皆无为普通应用
  2. META-INF/spring.factories 加载 ApplicationContextInitializerApplicationListener
  3. 从调用栈推断主类(deduceMainApplicationClass()

4.x 的构造逻辑与 3.x 一致,但模块边界重划:原 spring-boot 单体拆分为 spring-boot-webmvcspring-boot-jdbc 等按技术划分的模块,自动配置类随迁;Web starter 相应更名为 spring-boot-starter-webmvcspring-boot-starter-webflux。运行期的一个默认值变化是虚拟线程默认开启(spring.threads.virtual.enabled=true)。

运行阶段:run()

SpringApplication.run 的构造、环境准备、上下文刷新与运行后阶段;图中同时标出每一阶段已经具备的对象、对应事件及适合介入的扩展点。

事件是理解启动过程的钥匙:

事件发布时机
ApplicationStartingEventrun 刚开始,环境尚未准备
ApplicationEnvironmentPreparedEventEnvironment 创建后,配置文件在此时加载
ApplicationContextInitializedEvent上下文创建完毕、初始化器执行后
ApplicationPreparedEvent上下文准备完成,刷新之前
ContextRefreshedEventrefresh() 结束
ApplicationStartedEvent刷新完成,Runner 执行之前
ApplicationReadyEventRunner 执行完毕,应用对外可用

事件发布由 SpringApplicationRunListeners 完成:它从 spring.factories 加载 EventPublishingRunListener,把生命周期回调转换为 Spring 事件广播出去。

refresh():一切的核心

refreshContext() 最终调用 AbstractApplicationContext.refresh(),Spring 最经典的模板方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// AbstractApplicationContext#refresh()(节选)
public void refresh() {
synchronized (this.startupShutdownMonitor) {
prepareRefresh(); // 1 准备刷新
obtainFreshBeanFactory(); // 2 刷新 BeanFactory
prepareBeanFactory(beanFactory); // 3 填充标准依赖
postProcessBeanFactory(beanFactory); // 4 子类扩展点
// 5 ★ 解析配置类,注册 BeanDefinition
invokeBeanFactoryPostProcessors(beanFactory);
registerBeanPostProcessors(beanFactory); // 6 注册后置处理器
initMessageSource(); // 7 国际化
initApplicationEventMulticaster(); // 8 事件广播器
onRefresh(); // 9 创建 WebServer
registerListeners(); // 10 注册监听器
// 11 ★ 实例化全部非懒加载单例
finishBeanFactoryInitialization(beanFactory);
finishRefresh(); // 12 发布刷新完成事件
}
}

12 个步骤中,两步是理解 Bean 加载的关键:

  • 步骤 5:解析所有配置类,把 @Component@Bean 注册为 BeanDefinition——此时只是"图纸"
  • 步骤 11:实例化全部非懒加载单例——图纸变成真正的对象

Bean 加载原理

从配置类到 BeanDefinition

步骤 5 的执行者是 ConfigurationClassPostProcessor,整个 Spring 中最核心的 BeanDefinitionRegistryPostProcessor 实现:

1
2
3
4
5
6
7
8
9
10
11
12
invokeBeanFactoryPostProcessors(bf)
└─ ConfigurationClassPostProcessor
├─ parse() // 解析配置类
│ ├─ @ComponentScan
│ │ └─ doScan() 扫描 @Component
│ │ 直接注册为 BeanDefinition
│ ├─ @Import
│ │ ├─ ImportSelector(自动配置走这里)
│ │ └─ ImportBeanDefinitionRegistrar
│ ├─ @ImportResource // XML 导入
│ └─ @Bean 方法收集
└─ loadBeanDefinitions() // 统一注册

解析完成后,扫描到的组件与 @Bean 方法都以 BeanDefinition 的形式存入 DefaultListableBeanFactory。容器此时持有的是"图纸集合",还没有任何业务对象。

Bean 生命周期

步骤 11 中,每个单例 Bean 经历四个阶段:

Bean 生命周期中实例化、属性填充、初始化与销毁的先后关系;@Autowired 在属性赋值阶段完成,AOP 代理在初始化末尾创建。

三个容易被忽略的细节:

  • @Autowired 的解析者是 AutowiredAnnotationBeanPostProcessor,注入发生在属性赋值阶段,而不是实例化阶段
  • AOP 代理在初始化的最后一步创建,因此代理对象身上的 @PostConstruct 早已执行完毕
  • 循环依赖的破局点在实例化与属性赋值之间:提前暴露的早期引用由 getEarlyBeanReference() 提供,这就是三级缓存的由来

A 依赖 B、B 依赖 A 时,三级缓存如何按一级、二级、三级的查询顺序取出 A 的早期引用,最终再收敛为完成态单例;并标明构造器循环和 prototype 的边界。

后置处理器家族

生命周期的每一步都由后置处理器驱动:

接口扩展时机典型用途
BeanPostProcessor初始化前后代理创建、回调触发
InstantiationAwareBeanPostProcessor实例化前后、属性赋值前修改注入逻辑
MergedBeanDefinitionPostProcessor合并 BeanDefinition 后收集注入元数据
SmartInstantiationAwareBeanPostProcessor构造器推断、提前暴露引用三级缓存

Spring 内置的关键实现:

实现类职责
AutowiredAnnotationBeanPostProcessor@Autowired、@Value 注入
CommonAnnotationBeanPostProcessor@Resource、@PostConstruct、@PreDestroy
AbstractAutoProxyCreatorAOP 代理创建基类
AsyncAnnotationBeanPostProcessor@Async 异步代理
InfrastructureAdvisorAutoProxyCreator@Transactional 事务代理

自动配置原理

@EnableAutoConfiguration

1
2
3
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}

AutoConfigurationImportSelector 实现了 DeferredImportSelector——延迟到所有常规配置类解析完毕后才执行,保证用户的 Bean 定义先于自动配置注册。这是"用户配置永远优先"的实现基础。

自动配置从 EnableAutoConfiguration 导入候选、去重排除、按类路径和属性等条件筛选,到 ConditionalOnMissingBean 让位给用户 Bean 的完整决策过程。

Spring Boot 2.7 之前候选列表存放在 META-INF/spring.factories;之后迁移到 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,一行一个全限定类名。spring.factories 如今只负责加载监听器、初始化器等基础设施组件,4.x 依然保留这一职责。需要注意的是 4.x 迁移了部分类的包名——如 EnvironmentPostProcessororg.springframework.boot.env 移到 org.springframework.bootBootstrapRegistry 系列移到 org.springframework.boot.bootstrap——做深度集成的框架要同步更新 spring.factories 中的键。

条件注解

1
2
3
4
5
6
7
8
9
10
11
12
// 数据源自动配置(节选,示意)
@AutoConfiguration
@ConditionalOnClass(DataSource.class)
public class DataSourceAutoConfiguration {

@Bean
@ConditionalOnMissingBean(DataSource.class)
@ConfigurationProperties("spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
}

三类最常用的条件:

  • @ConditionalOnClass:类路径存在指定类才生效,自动配置的第一道门槛——依赖不在,整类跳过
  • @ConditionalOnMissingBean:容器中没有同类 Bean 才生效,用户自定义的配置永远赢
  • @ConditionalOnProperty:配置属性满足要求才生效

所有条件注解都由 @Conditional(OnClassCondition.class) 这类元注解驱动,条件评估统一收敛在 SpringBootCondition

1
2
3
4
5
6
7
8
9
// SpringBootCondition#matches(节选)
public final boolean matches(
ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionOutcome outcome =
getMatchOutcome(context, metadata);
// ...记录评估结果与日志
return outcome.isMatch();
}

每个 @ConditionalOnXxx 对应一个 Condition 实现,把判断逻辑写在 getMatchOutcome() 里。评估结果写入 ConditionEvaluationReport--debug 启动时打印的正是这份报告。

容器继承体系

两大接口体系

BeanFactory 是容器底层,ApplicationContext 是它的门面封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
BeanFactory                        // getBean
└─ HierarchicalBeanFactory // 支持父子容器
└─ AutowireCapableBeanFactory // 自动装配能力
└─ ConfigurableListableBeanFactory
└─ DefaultListableBeanFactory // 标准实现

ApplicationContext // 资源 + 事件 + 国际化
└─ ConfigurableApplicationContext
└─ AbstractApplicationContext // refresh() 骨架
├─ GenericApplicationContext
│ └─ AnnotationConfigApplicationContext
└─ AbstractRefreshableConfigApplicationContext
└─ GenericWebApplicationContext
└─ ServletWebServerApplicationContext
└─ AnnotationConfig
ServletWebServerApplicationContext

ApplicationContext 不重复造轮子:它内部持有一个 DefaultListableBeanFactory,所有 Bean 操作委托给它,自己只叠加事件发布、资源加载、国际化能力。

BeanDefinition 体系

1
2
3
4
5
6
7
8
9
10
BeanDefinition(接口)
├─ AnnotatedBeanDefinition // 携带注解元数据
└─ AbstractBeanDefinition(抽象基类)
├─ GenericBeanDefinition
│ └─ ScannedGenericBeanDefinition
// @ComponentScan 的扫描产物
├─ RootBeanDefinition
// 多个定义"合并"后的最终形态
└─ ConfigurationClassBeanDefinition
// @Configuration 类对应的定义

图中省略了交叉继承:ScannedGenericBeanDefinition 同时实现了 AnnotatedBeanDefinition。实例化之前,容器总是把 BeanDefinition 合并成 RootBeanDefinition 再使用。

贯穿始终的设计模式

  1. 模板方法refresh()AbstractApplicationContext 定死骨架,onRefresh() 留给子类——Boot 正是靠它创建 WebServer
  2. 观察者:事件机制贯穿启动全程,ApplicationListener 是解耦扩展的标准入口
  3. 策略WebApplicationType 决定上下文实现,BeanPostProcessor 家族决定各阶段行为
  4. 委托:ApplicationContext 把 Bean 操作委托给内部的 BeanFactory
  5. 工厂BeanFactory 统一管理 Bean 从定义到销毁的全生命周期

调试技巧

1
2
3
# 打印自动配置报告:哪些匹配、哪些被跳过及原因
java -jar app.jar --debug
# 或 application.yml 中设置 debug: true
1
2
3
4
5
6
7
8
9
10
11
// 打印容器中全部 Bean 定义
@Autowired
private ApplicationContext ctx;

public void printBeans() {
for (String name :
ctx.getBeanDefinitionNames()) {
System.out.println(
name + " -> " + ctx.getBean(name).getClass());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 观察 Bean 初始化的先后顺序
@Component
public class LifecycleLogger
implements BeanPostProcessor {

@Override
public Object postProcessBeforeInitialization(
Object bean, String beanName) {
System.out.println("Before: " + beanName);
return bean;
}

@Override
public Object postProcessAfterInitialization(
Object bean, String beanName) {
System.out.println("After: " + beanName);
return bean;
}
}

相关阅读