当前位置: 首页 > news >正文

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 在两者之间专门留了一个钩子,让你在一个所有基础设施都已就绪、但还没对外提供服务的黄金时间点执行初始化逻辑。

http://www.jsqmd.com/news/1263349/

相关文章:

  • web3.swift完全指南:从零开始构建Swift以太坊应用
  • oneAPI Math Library (oneMath)完全指南:从入门到精通的跨平台数学计算引擎
  • 为什么选择gimme?5分钟了解这款Go语言版本管理神器
  • Stellaris DLC Unlocker完全指南:支持30+DLC的免费工具使用教程
  • 【扣子测试用例机器人实战指南】:20年QA专家亲授3大自动化提效秘法,97%团队未掌握的智能用例生成逻辑
  • Jellium Desktop皮肤开发入门:创建自己的个性化界面
  • kallisto高级技巧:如何通过命令行参数优化转录组定量结果
  • 龙泉山卧龙寺公墓、成都公墓、公墓环境、价格、位置 - 速递信息
  • UnrealPak资源提取全攻略:从原理到实战,解锁虚幻引擎资源宝库
  • BOSS 直聘上的工作可靠吗?人力资源管理师深度测评,附靠谱求职平台推荐
  • 多模态大模型(MLLM)核心技术解析与实践指南
  • 从理论到实践:online_migrations配置指南 — 3步实现安全高效的PostgreSQL迁移
  • Hancitor木马解密工具使用指南:XOR加密流量分析与IOC提取
  • 高精度ADC校准与模式控制:ADS124S0x实战指南
  • 智能抓取系统OpenClaw Dreaming:机器视觉与强化学习的工业应用
  • Pygame实战:构建像素风RPG的角色移动与对话系统
  • 基于YOLOv12的血细胞检测系统开发与优化
  • Privileged 权限:你的容器真的需要吗?
  • 端云协同架构:移动AI性能优化关键技术解析
  • 欧米茄通知:2026年7月最新中国售后网点地址及热线电话 - 速递信息
  • rvs(rust-verb-shell):一款面向人类和 AI Agent 的结构化 Shell
  • Gemini 3.6 Flash 模型:轻量级多模态AI助手的核心能力与API实践
  • PHP+MySQL健康饮食推荐系统毕业设计全流程解析与实战
  • 如何快速掌握红队技术?Awesome-Red-Teaming资源库深度解析
  • 从OpenStreetMap到卫星图像:TkinterMapView切换地图瓦片服务器的实用方法
  • 从标准SPI到MibSPI:多缓冲模式与核心寄存器深度解析
  • ADC344x系列四通道14位ADC:高动态范围与低功耗设计解析
  • 基于YOLOv8的水稻病害实时检测系统设计与实践
  • 涂胶显影机(Track)首席专家级工程师完整JD(12维度)+ 对外简化版JD
  • Niagara与unreal-vdb结合教程:创建动态粒子与体积交互效果