news 2026/4/30 23:38:41

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/4/30 23:04:44

AI 扩图:从像素填充到场景延伸的技术逻辑

在数字内容生产中&#xff0c;用户常面临“图片尺寸不足”的痛点——从电商产品图需要高清放大&#xff0c;到老照片修复需要延伸场景&#xff0c;传统像素填充方法早已难以满足精细化需求。比如传统双线性插值处理1080P图片时&#xff0c;PSNR&#xff08;峰值信噪比&#xff…

作者头像 李华
网站建设 2026/4/30 23:37:02

CentOS7/8在线yum源自动设置

1. 前提说明CentOS7/8版本官方已经归档&#xff0c;默认的安装源无法使用&#xff0c;而且网上大多数提供的国内yum地址也大部分失效。故为了快速有效的使用在线yum&#xff0c;结合国内清华源&#xff0c;来生成有效的repo文件&#xff0c;帮助实施人员解决手动配置慢等问题。…

作者头像 李华
网站建设 2026/4/30 22:56:39

12月31日截止!2026年个税专项扣除确认

动动手明年工资可能变多哦&#xff5e;确认时间&#xff1a;12月1日—12月31日一定要记得操作&#xff01;错过的话明年1月起单位会暂停扣除&#xff0c;到手工资可能变少&#xff0c;后续补办超麻烦&#xff01;手机APP操作指南&#xff08;个人所得税APP&#xff09;&#xf…

作者头像 李华
网站建设 2026/4/30 22:56:38

C进阶之内存对齐,硬件总线和高并发伪共享的底层原理

在 C 语言的面试和实际开发中&#xff0c;sizeof 是一个出现频率极高的关键词。初学者往往认为它只是用来计算变量占用空间的&#xff0c;但实际上&#xff0c;sizeof 的背后隐藏着CPU 架构、硬件总线甚至高并发性能的秘密。 今天&#xff0c;我们不注重于对齐规则&#xff0c;…

作者头像 李华
网站建设 2026/4/22 1:37:00

Springboot基于JavaWeb的美食交流宣传系统的设计与实现_wl8bw269

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

作者头像 李华