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

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 容器
  • 优先使用事件驱动,而非硬编码调用

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

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

相关文章:

  • 2025年本安型低速图像处理摄像仪直销厂家权威推荐榜单:视频分析服务器‌/矿鸿设备‌/矿用本安型低速图像处理摄像仪源头厂家精选 - 品牌推荐官
  • 2025年兰州高性价比交通方便酒店口碑排行,出行便利住宿优选 - 工业推荐榜
  • 银行对账 RPA:全流程智能自动化,降本增效核心方案
  • 2025不锈钢管正规厂家TOP5权威推荐:甄选实力供应商助力 - myqiye
  • 2025年不锈钢管制造商年度排名:不锈钢管个性化定制与源头厂 - mypinpai
  • 2025年靠谱的收银机扫描抢/收银机钱箱TOP品牌厂家排行榜 - 品牌宣传支持者
  • 【AI总结】Axure实战:解决导航栏母版跳转后选中状态丢失问题
  • 把AI大模型想象成一个“超级猜词游戏”!非专业也能看懂的工作原理,原来这么简单!
  • 使用 Python 为 PDF 添加水印
  • 2025年口碑不错的酒柜定制专业公司推荐,有实力的酒柜定制公 - 工业品牌热点
  • vLLM推理引擎教程5-PagedAttention技术
  • 2025年靠谱的单染混纺纱/卫衣混纺纱厂家最新权威实力榜 - 品牌宣传支持者
  • 2025年口碑好的工业用超声波清洗设备厂家推荐及选购参考榜 - 品牌宣传支持者
  • 2025年上海工业品营销培训公司权威推荐榜单:工业品营销培训/工业品营销书籍/工业品营销咨询服务机构精选 - 品牌推荐官
  • 【万字长文】上下文工程最佳实践:打造高效AI应用的必学知识(建议收藏)!
  • AI 扩图:从像素填充到场景延伸的技术逻辑
  • 揭秘Web组件的隐形守护者:影子DOM如何彻底改变前端开发格局!
  • 数字化转型核心选型:CRM客户 / 销售 / 生产 / AI / 集成全维度能力解析
  • 读《构建之法》后的六个问题——解
  • 中山GEO优化:立足中山,辐射湾区,助力B2B企业区域品牌突围
  • Java 大视界 -- Java 大数据在智能教育虚拟仿真实验中的学生行为分析与实验效果评估
  • forEach异步问题
  • AI发展历程全解析:从规则系统到6710亿参数大模型,深入探讨大语言模型训练原理与全球AI竞争态势!
  • 2025年质量好的有机棉厂家最新推荐权威榜 - 品牌宣传支持者
  • 2025年质量好的驾驶式洗地机/工业洗地机优质厂家推荐榜单 - 品牌宣传支持者
  • 2025年比较好的直立棉床垫厂家推荐及采购参考 - 品牌宣传支持者
  • Wireshark 4.6.2 发布:修复两处安全漏洞,关键网络分析工具迎来重要更新
  • 大型点单收银系统+大型商城系统一体化大系统源码_OctShop
  • 2025年评价高的店铺收银软件/生鲜收银软件使用反馈好评率排行榜 - 品牌宣传支持者
  • 前端革命:自定义元素如何让HTML元素“活“起来,重构你的开发体验!