news 2026/6/23 12:03:49

Java中高级面试题详解(十五):彻底搞懂 Spring Boot 启动流程与扩展点,别再只会写 main 方法!

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java中高级面试题详解(十五):彻底搞懂 Spring Boot 启动流程与扩展点,别再只会写 main 方法!

视频看了几百小时还迷糊?关注我,几分钟让你秒懂!

SpringApplication.run(MyApp.class, args)一行代码背后,Spring Boot 做了上百个初始化操作
但很多开发者对启动过程一无所知,导致:

  • 想在启动时加载配置却不知道时机;
  • 自定义 Banner 被覆盖;
  • 启动失败只能看日志“猜”原因;
  • 无法优雅集成第三方框架。

今天我们就从源码 + 扩展点 + 实战案例三方面,彻底拆解 Spring Boot 启动全流程,并教你如何“插手”关键环节!


一、需求场景:应用启动时需完成三件事

  1. 从远程配置中心拉取动态配置;
  2. 初始化缓存数据(如字典表);
  3. 注册自定义健康检查(HealthIndicator)。

你尝试在@PostConstruct中做,但发现:

  • 配置中心客户端还没初始化;
  • 数据库连接池未就绪;
  • 健康检查未被 Spring Boot Actuator 识别。

因为你没用对“启动扩展点”!


二、Spring Boot 启动全流程(7 大阶段)

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) { return run(new Class<?>[] { primarySource }, args); } public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); // 核心! }

阶段1️⃣:推断应用类型

  • 判断是SERVLET(Web)、REACTIVE(WebFlux)还是NONE(普通应用);
  • 决定后续使用AnnotationConfigServletWebServerApplicationContext还是其他上下文。

阶段2️⃣:设置初始化器(Initializers)

  • spring.factories加载ApplicationContextInitializer
  • 用于在refresh 前修改 ApplicationContext(如设置 Environment)。

阶段3️⃣:设置监听器(Listeners)

  • 加载SpringApplicationRunListener(如EventPublishingRunListener);
  • 用于发布启动事件(如ApplicationStartedEvent)。

阶段4️⃣:推断主配置类

  • 找到带有@SpringBootApplication的类。

阶段5️⃣:创建 ApplicationContext

  • 根据应用类型创建对应上下文(如 Web 应用 →ServletWebServerApplicationContext)。

阶段6️⃣:准备上下文(prepareContext)

  • 绑定 Environment;
  • 应用 Initializers;
  • 注册单例 Bean(如springApplicationArguments);
  • 发布ApplicationContextInitializedEvent

阶段7️⃣:刷新上下文(refreshContext)

  • 调用AbstractApplicationContext.refresh()
  • 完成 BeanFactory 初始化、BeanPostProcessor 注册、Bean 实例化等;
  • 此时所有@Component@Service才真正创建!

💡关键结论

  • Environment 就绪→ 在prepareContext阶段;
  • 所有 Bean 可用→ 在refresh完成后;
  • Web 服务启动→ 在refresh最后一步(启动内嵌 Tomcat)。

三、6 大核心扩展点实战

✅ 扩展点1:ApplicationContextInitializer

用途:在 ApplicationContext refresh 前修改其属性。

public class CustomContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext applicationContext) { // 例如:添加 PropertySource ConfigurableEnvironment env = applicationContext.getEnvironment(); Map<String, Object> remoteProps = fetchFromConfigCenter(); env.getPropertySources().addFirst(new MapPropertySource("remote", remoteProps)); } }

注册方式(二选一):

  • 方式1:META-INF/spring.factories
    org.springframework.context.ApplicationContextInitializer=\ com.example.CustomContextInitializer
  • 方式2:主类中手动添加
    SpringApplication app = new SpringApplication(MyApp.class); app.addInitializers(new CustomContextInitializer()); app.run(args);

📌 适用场景:动态配置注入、环境变量增强


✅ 扩展点2:SpringApplicationRunListener

用途:监听启动各阶段事件(最底层扩展点)。

public class CustomRunListener implements SpringApplicationRunListener { public CustomRunListener(SpringApplication application, String[] args) { } @Override public void started(ConfigurableBootstrapContext bootstrapContext, SpringApplicationRunListener.StartupStep step) { System.out.println("【启动开始】"); } @Override public void ready(ConfigurableApplicationContext context, SpringApplicationRunListener.ReadyStep step) { System.out.println("【应用就绪,Web 服务已启动】"); } }

注册:必须通过spring.factories

org.springframework.boot.SpringApplicationRunListener=\ com.example.CustomRunListener

⚠️ 注意:构造函数签名必须匹配!


✅ 扩展点3:ApplicationRunner/CommandLineRunner

用途:在所有 Bean 初始化完成后、Web 服务启动前执行逻辑。

@Component public class CacheDataRunner implements ApplicationRunner { @Autowired private DictService dictService; @Override public void run(ApplicationArguments args) throws Exception { dictService.loadAllIntoCache(); // 此时 DB 连接池已就绪! } }

💡ApplicationRunner使用ApplicationArguments,比CommandLineRunner更安全。


✅ 扩展点4:@EventListener监听启动事件

用途:响应特定生命周期事件。

@Component public class StartupEventListener { @EventListener public void handleContextRefreshed(ContextRefreshedEvent event) { // 所有 Bean 已创建完成 System.out.println("Context refreshed!"); } @EventListener public void handleReady(ApplicationReadyEvent event) { // Web 服务已启动,可接收请求 System.out.println("Application is READY!"); } }
事件时机
ApplicationStartingEvent启动开始,日志系统未初始化
ApplicationEnvironmentPreparedEventEnvironment 准备好
ApplicationContextInitializedEventApplicationContext 初始化完成,Bean 未加载
ApplicationPreparedEventBean 已注册,未实例化
ApplicationStartedEventBean 已创建,Runner 未执行
ApplicationReadyEvent一切就绪,可处理请求

✅ 扩展点5:SmartLifecycle

用途:控制自定义组件的启动/关闭顺序。

@Component public class CustomJobScheduler implements SmartLifecycle { private boolean running = false; @Override public void start() { // 应用就绪后启动定时任务 System.out.println("启动自定义调度器"); this.running = true; } @Override public void stop() { System.out.println("停止调度器"); this.running = false; } @Override public boolean isRunning() { return this.running; } // 控制启动顺序(值越小越早启动) @Override public int getPhase() { return Integer.MAX_VALUE - 1; // 在最后启动 } }

✅ 扩展点6:HealthIndicator

用途:自定义健康检查(配合 Actuator)。

@Component public class CustomCacheHealthIndicator implements HealthIndicator { @Override public Health health() { if (isCacheConnected()) { return Health.up().withDetail("cache", "OK").build(); } return Health.down().withDetail("cache", "Disconnected").build(); } }

访问http://localhost:8080/actuator/health即可看到结果。


四、反例:错误的初始化时机

❌ 在@PostConstruct中访问数据库

@Service public class BadService { @PostConstruct public void init() { // 此时 DataSource 可能未初始化! jdbcTemplate.query(...); // 可能 NPE 或报错 } }

✅ 正确做法:用ApplicationRunner@EventListener(ApplicationReadyEvent.class)


❌ 在静态代码块中读取配置

public class ConfigHolder { static { // Environment 还没准备好! String value = System.getProperty("my.config"); // 只能读 JVM 参数 } }

✅ 正确做法:通过@Value@ConfigurationProperties注入。


五、面试加分回答

问:Spring Boot 启动时,Banner 是什么时候打印的?

✅ 回答:

SpringApplication.run()的早期阶段,
位于ApplicationStartingEvent之后、Environment准备之前。
具体由BannerPrinter类实现,
可通过SpringApplication.setBanner()banner.txt自定义。

问:如何确保某个 Bean 在 Tomcat 启动后再初始化?

✅ 回答:

使用@EventListener(ApplicationReadyEvent.class)
因为ApplicationReadyEvent发布时,
内嵌 Web 容器(如 Tomcat)已经成功启动并绑定端口。


六、最佳实践建议

  • 动态配置→ 用ApplicationContextInitializer
  • 数据预热→ 用ApplicationRunner
  • 启动通知→ 用ApplicationReadyEvent
  • 资源管理→ 实现SmartLifecycle
  • 避免在构造器/静态块中依赖 Spring 容器
  • 优先使用事件驱动,而非硬编码调用

视频看了几百小时还迷糊?关注我,几分钟让你秒懂!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/22 22:40:33

CTF 解题核心思维 + 新手入门全攻略

CTF 解题核心思维 新手入门全攻略 很多人入门时把精力全花在工具操作上&#xff0c;却忽略了 CTF 的核心是思维逻辑 而非操作技巧。 本文跳出工具罗列 题型堆砌的传统思路&#xff0c;从解题核心思维链、新手三阶入门路径、实战案例拆解三大维度&#xff0c;帮你建立看到题目…

作者头像 李华
网站建设 2026/6/23 1:04:17

2026PCB产业高端化浪潮与慕尼黑上海电子展的连接枢纽

近年来&#xff0c;全球电子制造产业链加速重构&#xff0c;亚洲地区凭借在劳动力、资源、政策和产业聚集方面的突出优势&#xff0c;成为全球PCB&#xff08;印刷电路板&#xff09;产能转移的核心目的地。自2006年以来&#xff0c;中国大陆地区的PCB产量和产值一直居于世界前…

作者头像 李华
网站建设 2026/6/23 0:30:01

揭秘Web组件的隐形守护者:影子DOM如何彻底改变前端开发格局!

你是否曾经在项目中被CSS样式冲突折磨得焦头烂额&#xff1f;是否在团队协作中因为样式污染而陷入无尽的调试漩涡&#xff1f;今天&#xff0c;我要向你揭示一个被大多数前端开发者忽视的宝藏技术——影子DOM&#xff08;Shadow DOM&#xff09;&#xff0c;它不仅能彻底解决样…

作者头像 李华
网站建设 2026/6/23 21:00:49

AI基于Springboot的图书馆在线占座系统_s58324g1

目录具体实现截图项目介绍论文大纲核心代码部分展示项目运行指导结论源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可合作具体实现截图 本系统&#xff08;程序源码数据库调试部署讲解&#xff09;同时还支持java、ThinkPHP、Node.js、Spring B…

作者头像 李华
网站建设 2026/6/22 22:47:26

从零构建Agent:大模型智能代理的六步落地指南!

简介 文章介绍了构建高质量Agent的六步完整方法论&#xff1a;1)用具体示例定义任务&#xff1b;2)设计操作流程(SOP)&#xff1b;3)用提示词构建MVP&#xff1b;4)连接与编排&#xff1b;5)测试与迭代&#xff1b;6)部署扩展与优化。通过邮件代理案例&#xff0c;强调从小处着…

作者头像 李华
网站建设 2026/6/23 10:28:57

股票历史分时BOLL数据之Python、Java等多种主流语言实例代码演示通过股票数据接口获取数据

在金融科技飞速发展的当下&#xff0c;股票数据API成为投资者、开发者和金融分析师获取股票市场的重要工具。无论是在开发量化交易策略&#xff0c;还是进入深入的金融数据分析&#xff0c;选一个合适的股票数据API至关重要。在量化分析领域&#xff0c;实时、准确的接口太重要…

作者头像 李华