咸阳市网站建设_网站建设公司_网站备案_seo优化
2025/12/20 9:57:40 网站建设 项目流程

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

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询