SpringBoot33-Spring Boot 的启动顺序(启动生命周期)
一、Spring Boot 启动的完整时间线
我们从main方法第一行开始,按顺序往下走:
┌─────────────────────────────────────────────────────────────┐ │ 步骤 1: main() 方法开始 │ │ SpringApplication.run(DemoApplication.class, args); │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 步骤 2: 创建 SpringApplication 实例 │ │ 解析 @SpringBootApplication,确定是 Web 应用还是普通应用 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 步骤 3: 创建 ApplicationContext(应用上下文) │ │ 这就是 Spring 容器本身,此时它是一个"空盒子" │ │ 里面还没有任何 Bean 实例 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 步骤 4: 加载 Bean 定义(Bean Definition) │ │ Spring 扫描你的代码,发现: │ │ - @Component, @Service, @Repository, @Controller │ │ - @Bean 方法 │ │ 但它只是"记下来":有个类叫 UserService,需要创建一个单例 │ │ 此时并没有调用 new UserService() │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 步骤 5: 刷新上下文(context.refresh())← 最核心的一步 │ │ │ │ 这个 refresh() 方法内部又分成多个子步骤: │ │ │ │ 5.1 执行 BeanFactoryPostProcessor │ │ (比如解析 @ConfigurationProperties 占位符 ${}) │ │ │ │ 5.2 注册 BeanPostProcessor │ │ (这些后处理器会在每个 Bean 创建前后插入逻辑) │ │ │ │ 5.3 实例化 Bean(调用构造方法 new 出来) │ │ 比如 new UserService() │ │ │ │ 5.4 依赖注入(给 @Autowired 字段赋值) │ │ 比如 userService.userDao = 刚才创建的 UserDao 实例 │ │ │ │ 5.5 调用 Aware 接口(如 ApplicationContextAware) │ │ 让 Bean 知道自己活在哪个容器里 │ │ │ │ 5.6 调用 BeanPostProcessor.postProcessBeforeInitialization() │ │ │ │ 5.7 调用初始化方法 │ │ - @PostConstruct 标注的方法 │ │ - InitializingBean.afterPropertiesSet() │ │ 【你之前问的"Bean 实例化并完成依赖注入"就是到这里结束】 │ │ │ │ 5.8 调用 BeanPostProcessor.postProcessAfterInitialization() │ │ (比如 AOP 代理对象就是在这里生成的) │ │ │ │ 5.9 所有单例 Bean 创建完毕 │ │ │ │ 5.10 发布 ContextRefreshedEvent │ │ │ │ refresh() 方法执行完毕 ← 这就是"Spring 上下文完全刷新" │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 步骤 6: 执行 ApplicationRunner.run() / CommandLineRunner │ │ 【你之前问的"什么时候执行 run 方法"就是这里】 │ │ 此时所有 Bean 都已创建、注入、初始化完成 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 步骤 7: 发布 ApplicationReadyEvent │ │ 通知所有监听器:应用已完全就绪 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 步骤 8: SpringApplication.run() 返回 │ │ 如果是 Web 应用,Tomcat 开始接收 HTTP 请求 │ └─────────────────────────────────────────────────────────────┘二、回答你提到的几个关键概念
1. "Bean 实例化并完成依赖注入" 是什么时候?
就是上面时间线步骤 5.3 ~ 5.4:
// 5.3 实例化:Spring 调用构造方法 UserService userService = new UserService(); // 5.4 依赖注入:Spring 通过反射给字段赋值 userService.userDao = userDaoInstance; // 相当于 @Autowired到这一步结束时,对象已经存在,且它依赖的其他对象也已经填充进去了。但注意:此时 Spring 容器的 refresh() 还没走完,可能还有一些后处理器没执行(比如生成 AOP 代理)。
2. "Spring 上下文刷新" 到底是什么?
"刷新"(refresh)是 Spring 容器的一个核心方法名,它负责从头开始初始化整个容器。
你可以把它理解为一个"工厂开工"的过程:
之前只是画了图纸(Bean 定义)
refresh() 就是按图纸把产品全部造出来、组装好、质检完成
为什么叫"刷新"不叫"初始化"?
因为ApplicationContext的设计允许你调用refresh()多次(虽然 Spring Boot 里通常只调一次),每次调用都会销毁旧的、重新创建新的,所以叫"刷新"更准确。
3. "Spring 上下文完全刷新" 又是什么时候?
就是context.refresh()方法执行完毕的那一刻。
此时:
所有单例 Bean 都已经创建(new 过了)
所有依赖都已经注入(@Autowired 填过了)
所有 @PostConstruct 都已经调用过了
所有 BeanPostProcessor 都已经处理过了
AOP 代理已经生成
事件广播器已经就绪
但此时 ApplicationRunner 还没执行。
4. ApplicationRunner.run() 到底在什么时候?
在context.refresh()完全结束之后,SpringApplication.run()返回之前。
看 Spring Boot 源码的简化逻辑:
public ConfigurableApplicationContext run(String... args) { // 1. 创建上下文 ConfigurableApplicationContext context = createApplicationContext(); // 2. 准备环境、加载配置 prepareEnvironment(...); // 3. 刷新上下文(里面包含实例化、注入、@PostConstruct) refreshContext(context); // ← 执行到这里,所有 Bean 都已就绪,上下文已完全刷新 // 4. 执行所有 Runner callRunners(context, applicationArguments); // ← 你的 ApplicationRunner.run() 就是在这里被调用的 // 5. 发布就绪事件 listeners.started(context); // 6. 返回上下文 return context; }三、为什么要分成这些阶段?(设计原因)
如果没有这些明确的阶段划分,会有什么问题?
| 阶段 | 如果没有这个阶段 | 弊端 |
|---|---|---|
| 步骤 4:只加载定义,不实例化 | 扫描到一个类就立即 new | 如果 A 依赖 B,但 B 还没扫描到,A 实例化时会因为找不到 B 而报错 |
| 步骤 5.3~5.4:先实例化,再注入 | 构造方法里就要求依赖可用 | 循环依赖(A 依赖 B,B 依赖 A)无法解决;而且构造方法里不能安全地使用 @Autowired 字段 |
| 步骤 5.7:@PostConstruct 在注入后调用 | 在注入前调用初始化方法 | 方法里用到 @Autowired 字段时是 null,直接空指针异常 |
| 步骤 5.10:refresh 完成后再执行 Runner | 在 @PostConstruct 里做启动任务 | 此时 AOP 代理可能还没生成,事务可能还没生效,而且你无法确定其他 Bean 是否已初始化 |
| 步骤 6:Runner 在 refresh 后、run 返回前 | 把启动逻辑写在 main 方法里 | 无法利用 Spring 的依赖注入管理启动任务,代码耦合,难以测试 |
四、用一个具体例子验证顺序
你可以自己跑一下这段代码,看看控制台输出顺序:
@Component public class OrderDemo implements ApplicationRunner, InitializingBean, ApplicationContextAware { @Autowired private Environment env; public OrderDemo() { System.out.println("1. 构造方法:实例化 Bean"); } @Override public void setApplicationContext(ApplicationContext applicationContext) { System.out.println("2. Aware 接口:注入上下文"); } @PostConstruct public void postConstruct() { System.out.println("3. @PostConstruct:依赖注入已完成,env = " + (env != null)); } @Override public void afterPropertiesSet() { System.out.println("4. InitializingBean:初始化完成"); } @Override public void run(ApplicationArguments args) { System.out.println("5. ApplicationRunner:上下文已完全刷新"); } }输出一定是:
1. 构造方法:实例化 Bean 2. Aware 接口:注入上下文 3. @PostConstruct:依赖注入已完成,env = true 4. InitializingBean:初始化完成 5. ApplicationRunner:上下文已完全刷新五、总结
你问的这几个问题,确实是在讲Spring Boot 的启动顺序。核心记住这个链条:
加载 Bean 定义 → 实例化 → 依赖注入 →@PostConstruct → 上下文 refresh 完成 → ApplicationRunner→ 应用就绪
ApplicationRunner之所以存在,就是因为在refresh()完成之前,很多事情还没准备好(比如 AOP 代理、某些后处理器),而在run()返回之后,应用已经开始对外服务了。
所以 Spring Boot 在两者之间专门留了一个钩子,让你在一个所有基础设施都已就绪、但还没对外提供服务的黄金时间点执行初始化逻辑。
