2025/12/29 17:24:00
网站建设
项目流程
网站建设攵金手指专业,教育网站官网入口,广告服务平台,广东购物网站建设Spring Boot 钩子全集实战#xff08;二#xff09;#xff1a;SpringApplicationRunListener.starting() 详解
在上一篇中#xff0c;我们介绍了如何通过 addInitializers、addListeners 等方式在启动前注入逻辑。今天#xff0c;我们将聚焦于 Spring Boot 启动流程中最早…Spring Boot 钩子全集实战二SpringApplicationRunListener.starting()详解在上一篇中我们介绍了如何通过addInitializers、addListeners等方式在启动前注入逻辑。今天我们将聚焦于 Spring Boot 启动流程中最早触发的扩展点——SpringApplicationRunListener.starting()并重点讲解它在真实生产系统中的典型应用。一、什么是starting()starting()是SpringApplicationRunListener接口的第一个回调方法在以下条件下被调用日志系统已初始化可安全输出日志但 Environment 尚未加载application.yml还没读ApplicationContext 尚未创建Spring 容器完全不存在。✅这意味着这是整个应用生命周期中你能介入的“最早时刻”。在生产环境中这个“黄金窗口期”常被用于执行与业务无关但对系统稳定性至关重要的底层操作。二、场景 1全链路启动耗时监控定位启动性能瓶颈业务痛点生产环境中应用启动慢是高频问题但常规日志只能看到总耗时无法定位 “哪个阶段拖慢了启动”比如环境加载慢、Bean 初始化慢、Runner 执行慢排查效率极低。解决方案基于SpringApplicationRunListener记录每个启动阶段的耗时输出结构化监控日志精准定位瓶颈。实现代码importorg.springframework.boot.ConfigurableBootstrapContext;importorg.springframework.boot.SpringApplication;importorg.springframework.boot.SpringApplicationRunListener;importorg.springframework.context.ConfigurableApplicationContext;importorg.springframework.core.env.ConfigurableEnvironment;importjava.time.Duration;importjava.time.LocalDateTime;importjava.util.HashMap;importjava.util.Map;/** * 生产级启动耗时监控监听器 */publicclassStartupTimeMonitorListenerimplementsSpringApplicationRunListener{// 记录各阶段开始时间privatefinalMapString,LocalDateTimestageStartTimenewHashMap();privatefinalSpringApplicationapplication;privatefinalString[]args;// 必须的构造方法publicStartupTimeMonitorListener(SpringApplicationapplication,String[]args){this.applicationapplication;this.argsargs;}Overridepublicvoidstarting(ConfigurableBootstrapContextbootstrapContext){// 记录启动开始时间stageStartTime.put(starting,LocalDateTime.now());logStage(启动开始,应用启动流程初始化);}OverridepublicvoidenvironmentPrepared(ConfigurableBootstrapContextbootstrapContext,ConfigurableEnvironmentenvironment){recordStageCost(starting,environmentPrepared);logStage(环境准备完成,配置文件/环境变量加载完毕当前环境String.join(,,environment.getActiveProfiles()));}OverridepublicvoidcontextPrepared(ConfigurableApplicationContextcontext){recordStageCost(environmentPrepared,contextPrepared);logStage(上下文准备完成,ApplicationContext创建完毕未加载Bean定义);}OverridepublicvoidcontextLoaded(ConfigurableApplicationContextcontext){recordStageCost(contextPrepared,contextLoaded);logStage(上下文加载完成,Bean定义已注册等待上下文刷新);}Overridepublicvoidstarted(ConfigurableApplicationContextcontext,DurationtimeTaken){recordStageCost(contextLoaded,started);logStage(应用启动完成,上下文刷新完毕Runner未执行累计耗时timeTaken.toMillis()ms);}Overridepublicvoidready(ConfigurableApplicationContextcontext,DurationtimeTaken){recordStageCost(started,ready);logStage(应用完全就绪,所有Runner执行完毕可对外提供服务总耗时timeTaken.toMillis()ms);// 输出全链路耗时汇总生产环境可上报到监控平台如Prometheus/GrafanaSystem.out.println( 启动阶段耗时汇总 );stageStartTime.forEach((stage,time)-{longcostDuration.between(time,LocalDateTime.now()).toMillis();System.out.println(stage 阶段累计耗时costms);});}// 记录两个阶段之间的耗时privatevoidrecordStageCost(StringprevStage,StringcurrentStage){LocalDateTimeprevTimestageStartTime.get(prevStage);if(prevTime!null){longcostDuration.between(prevTime,LocalDateTime.now()).toMillis();stageStartTime.put(currentStage,LocalDateTime.now());System.out.println(prevStage - currentStage 耗时costms);}}// 结构化日志输出生产环境建议用SLF4JprivatevoidlogStage(Stringstage,Stringdesc){System.out.printf([%s] %s - %s%n,LocalDateTime.now(),stage,desc);}}配置加载在resources/META-INF/spring.factories中配置org.springframework.boot.SpringApplicationRunListener\ com.example.demo.listener.StartupTimeMonitorListener输出/Library/Java/JavaVirtualMachines/jdk-21.jdk/Contents/Home/bin/java -agentlib:jdwptransportdt_socket,address127.0.0.1:54168,suspendy,servern -javaagent:/Users/wangmingfei/Library/Caches/JetBrains/IdeaIC2024.3/captureAgent/debugger-agent.jar -Dkotlinx.coroutines.debug.enable.creation.stack.tracefalse -Ddebugger.agent.enable.coroutinestrue -Dkotlinx.coroutines.debug.enable.flows.stack.tracetrue -Dkotlinx.coroutines.debug.enable.mutable.state.flows.stack.tracetrue -Dfile.encodingUTF-8 -Dsun.stdout.encodingUTF-8 -Dsun.stderr.encodingUTF-8 -classpath /Users/wangmingfei/Documents/个人/05 java天梯之路/01 源码/03 每日打卡系列/daily-check-in/springboot钩子/demo/target/classes:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-starter-web/3.5.8/spring-boot-starter-web-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-starter/3.5.8/spring-boot-starter-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot/3.5.8/spring-boot-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-autoconfigure/3.5.8/spring-boot-autoconfigure-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-starter-logging/3.5.8/spring-boot-starter-logging-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/ch/qos/logback/logback-classic/1.5.21/logback-classic-1.5.21.jar:/usr/local/apache-maven-3.9.9/repository/ch/qos/logback/logback-core/1.5.21/logback-core-1.5.21.jar:/usr/local/apache-maven-3.9.9/repository/org/apache/logging/log4j/log4j-to-slf4j/2.24.3/log4j-to-slf4j-2.24.3.jar:/usr/local/apache-maven-3.9.9/repository/org/apache/logging/log4j/log4j-api/2.24.3/log4j-api-2.24.3.jar:/usr/local/apache-maven-3.9.9/repository/org/slf4j/jul-to-slf4j/2.0.17/jul-to-slf4j-2.0.17.jar:/usr/local/apache-maven-3.9.9/repository/jakarta/annotation/jakarta.annotation-api/2.1.1/jakarta.annotation-api-2.1.1.jar:/usr/local/apache-maven-3.9.9/repository/org/yaml/snakeyaml/2.4/snakeyaml-2.4.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-starter-json/3.5.8/spring-boot-starter-json-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/core/jackson-databind/2.19.4/jackson-databind-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/core/jackson-annotations/2.19.4/jackson-annotations-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/core/jackson-core/2.19.4/jackson-core-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.19.4/jackson-datatype-jdk8-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.19.4/jackson-datatype-jsr310-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.19.4/jackson-module-parameter-names-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-starter-tomcat/3.5.8/spring-boot-starter-tomcat-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/org/apache/tomcat/embed/tomcat-embed-core/10.1.49/tomcat-embed-core-10.1.49.jar:/usr/local/apache-maven-3.9.9/repository/org/apache/tomcat/embed/tomcat-embed-el/10.1.49/tomcat-embed-el-10.1.49.jar:/usr/local/apache-maven-3.9.9/repository/org/apache/tomcat/embed/tomcat-embed-websocket/10.1.49/tomcat-embed-websocket-10.1.49.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-web/6.2.14/spring-web-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-beans/6.2.14/spring-beans-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/io/micrometer/micrometer-observation/1.15.6/micrometer-observation-1.15.6.jar:/usr/local/apache-maven-3.9.9/repository/io/micrometer/micrometer-commons/1.15.6/micrometer-commons-1.15.6.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-webmvc/6.2.14/spring-webmvc-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-aop/6.2.14/spring-aop-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-context/6.2.14/spring-context-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-expression/6.2.14/spring-expression-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/slf4j/slf4j-api/2.0.17/slf4j-api-2.0.17.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-core/6.2.14/spring-core-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-jcl/6.2.14/spring-jcl-6.2.14.jar:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar com.example.demo.DemoApplication 已连接到地址为 127.0.0.1:54168传输: 套接字 的目标虚拟机 [2025-12-10T14:56:40.087923] 启动开始 - 应用启动流程初始化 starting - environmentPrepared 耗时128ms [2025-12-10T14:56:40.217733] 环境准备完成 - 配置文件/环境变量加载完毕当前环境 . ____ _ __ _ _ /\\ / ____ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | _ | _| | _ \/ _ | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) |____| .__|_| |_|_| |_\__, | / / / / |_||___//_/_/_/ :: Spring Boot :: (v3.5.8) environmentPrepared - contextPrepared 耗时31ms [2025-12-10T14:56:40.247800] 上下文准备完成 - ApplicationContext创建完毕未加载Bean定义 2025-12-10T14:56:40.24908:00 INFO 7823 --- [demo] [ main] com.example.demo.DemoApplication : Starting DemoApplication using Java 21.0.9 with PID 7823 (/Users/wangmingfei/Documents/个人/05 java天梯之路/01 源码/03 每日打卡系列/daily-check-in/springboot钩子/demo/target/classes started by wangmingfei in /Users/wangmingfei/Documents/个人/05 java天梯之路/01 源码/03 每日打卡系列/daily-check-in/springboot钩子/demo) 2025-12-10T14:56:40.25008:00 INFO 7823 --- [demo] [ main] com.example.demo.DemoApplication : No active profile set, falling back to 1 default profile: default contextPrepared - contextLoaded 耗时21ms [2025-12-10T14:56:40.269456] 上下文加载完成 - Bean定义已注册等待上下文刷新 2025-12-10T14:56:40.54408:00 INFO 7823 --- [demo] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http) 2025-12-10T14:56:40.55008:00 INFO 7823 --- [demo] [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2025-12-10T14:56:40.55008:00 INFO 7823 --- [demo] [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.49] 2025-12-10T14:56:40.56908:00 INFO 7823 --- [demo] [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2025-12-10T14:56:40.57008:00 INFO 7823 --- [demo] [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 301 ms 2025-12-10T14:56:40.70408:00 INFO 7823 --- [demo] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path / 2025-12-10T14:56:40.71008:00 INFO 7823 --- [demo] [ main] com.example.demo.DemoApplication : Started DemoApplication in 0.642 seconds (process running for 0.786) contextLoaded - started 耗时441ms [2025-12-10T14:56:40.711450] 应用启动完成 - 上下文刷新完毕Runner未执行累计耗时642ms started - ready 耗时0ms [2025-12-10T14:56:40.712131] 应用完全就绪 - 所有Runner执行完毕可对外提供服务总耗时643ms 启动阶段耗时汇总 ready 阶段累计耗时0ms environmentPrepared 阶段累计耗时496ms started 阶段累计耗时1ms starting 阶段累计耗时625ms contextPrepared 阶段累计耗时465ms contextLoaded 阶段累计耗时443ms生产价值精准定位启动瓶颈比如发现environmentPrepared阶段耗时久可排查配置中心拉取配置慢的问题输出结构化耗时日志可接入 ELK 进行可视化分析为启动性能优化提供数据支撑比如优化 Bean 初始化、减少配置加载项。三、场景 2启动失败全链路告警及时止损业务痛点生产环境中应用启动失败若不能及时发现会导致服务不可用且排查滞后。常规日志告警无法覆盖 “上下文未创建就失败” 的场景告警不全面。解决方案基于failed方法实现全场景启动失败告警结合企业微信 / 钉钉机器人推送告警信息包含失败阶段、异常栈、机器信息等关键内容。核心实现importorg.springframework.boot.SpringApplicationRunListener;importorg.springframework.context.ConfigurableApplicationContext;publicclassStartupFailerMonitorListenerimplementsSpringApplicationRunListener{Overridepublicvoidfailed(ConfigurableApplicationContextcontext,Throwableexception){// 1. 收集核心告警信息StringalertContentbuildAlertContent(context,exception);// 2. 推送告警生产环境建议异步推送try{System.out.println(String.format({%s}-{%s},【生产环境应用启动失败】,alertContent));}catch(Exceptione){System.err.println(告警推送失败e.getMessage());}// 3. 释放已初始化的资源避免资源泄漏releaseResources(context);}// 构建告警内容包含生产环境关键信息privateStringbuildAlertContent(ConfigurableApplicationContextcontext,Throwableexception){StringBuildersbnewStringBuilder();sb.append(机器IP).append(127.0.0.1).append(\n);sb.append(应用名称).append(测试).append(\n);sb.append(失败阶段).append(getFailedStage(context)).append(\n);sb.append(异常信息).append(exception.getMessage()).append(\n);sb.append(上下文状态).append(contextnull?未创建:已创建但刷新失败).append(\n);returnsb.toString();}// 释放资源比如关闭已创建的数据库连接、缓存客户端privatevoidreleaseResources(ConfigurableApplicationContextcontext){if(context!nullcontext.isActive()){try{// 关闭自定义资源池System.out.println(关闭资源成功);}catch(Exceptione){System.err.println(资源释放失败e.getMessage());}}}privateStringgetFailedStage(ConfigurableApplicationContextcontext){return启动阶段;}}构造失败场景spring: application: name: demo # 故意添加非法语法 xxxxx输出/Library/Java/JavaVirtualMachines/jdk-21.jdk/Contents/Home/bin/java -agentlib:jdwptransportdt_socket,address127.0.0.1:54261,suspendy,servern -javaagent:/Users/wangmingfei/Library/Caches/JetBrains/IdeaIC2024.3/captureAgent/debugger-agent.jar -Dkotlinx.coroutines.debug.enable.creation.stack.tracefalse -Ddebugger.agent.enable.coroutinestrue -Dkotlinx.coroutines.debug.enable.flows.stack.tracetrue -Dkotlinx.coroutines.debug.enable.mutable.state.flows.stack.tracetrue -Dfile.encodingUTF-8 -Dsun.stdout.encodingUTF-8 -Dsun.stderr.encodingUTF-8 -classpath /Users/wangmingfei/Documents/个人/05 java天梯之路/01 源码/03 每日打卡系列/daily-check-in/springboot钩子/demo/target/classes:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-starter-web/3.5.8/spring-boot-starter-web-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-starter/3.5.8/spring-boot-starter-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot/3.5.8/spring-boot-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-autoconfigure/3.5.8/spring-boot-autoconfigure-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-starter-logging/3.5.8/spring-boot-starter-logging-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/ch/qos/logback/logback-classic/1.5.21/logback-classic-1.5.21.jar:/usr/local/apache-maven-3.9.9/repository/ch/qos/logback/logback-core/1.5.21/logback-core-1.5.21.jar:/usr/local/apache-maven-3.9.9/repository/org/apache/logging/log4j/log4j-to-slf4j/2.24.3/log4j-to-slf4j-2.24.3.jar:/usr/local/apache-maven-3.9.9/repository/org/apache/logging/log4j/log4j-api/2.24.3/log4j-api-2.24.3.jar:/usr/local/apache-maven-3.9.9/repository/org/slf4j/jul-to-slf4j/2.0.17/jul-to-slf4j-2.0.17.jar:/usr/local/apache-maven-3.9.9/repository/jakarta/annotation/jakarta.annotation-api/2.1.1/jakarta.annotation-api-2.1.1.jar:/usr/local/apache-maven-3.9.9/repository/org/yaml/snakeyaml/2.4/snakeyaml-2.4.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-starter-json/3.5.8/spring-boot-starter-json-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/core/jackson-databind/2.19.4/jackson-databind-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/core/jackson-annotations/2.19.4/jackson-annotations-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/core/jackson-core/2.19.4/jackson-core-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.19.4/jackson-datatype-jdk8-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.19.4/jackson-datatype-jsr310-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.19.4/jackson-module-parameter-names-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-starter-tomcat/3.5.8/spring-boot-starter-tomcat-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/org/apache/tomcat/embed/tomcat-embed-core/10.1.49/tomcat-embed-core-10.1.49.jar:/usr/local/apache-maven-3.9.9/repository/org/apache/tomcat/embed/tomcat-embed-el/10.1.49/tomcat-embed-el-10.1.49.jar:/usr/local/apache-maven-3.9.9/repository/org/apache/tomcat/embed/tomcat-embed-websocket/10.1.49/tomcat-embed-websocket-10.1.49.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-web/6.2.14/spring-web-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-beans/6.2.14/spring-beans-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/io/micrometer/micrometer-observation/1.15.6/micrometer-observation-1.15.6.jar:/usr/local/apache-maven-3.9.9/repository/io/micrometer/micrometer-commons/1.15.6/micrometer-commons-1.15.6.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-webmvc/6.2.14/spring-webmvc-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-aop/6.2.14/spring-aop-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-context/6.2.14/spring-context-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-expression/6.2.14/spring-expression-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/slf4j/slf4j-api/2.0.17/slf4j-api-2.0.17.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-core/6.2.14/spring-core-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-jcl/6.2.14/spring-jcl-6.2.14.jar:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar com.example.demo.DemoApplication 已连接到地址为 127.0.0.1:54261传输: 套接字 的目标虚拟机 [2025-12-10T14:58:32.464881] 启动开始 - 应用启动流程初始化 {【生产环境应用启动失败】}-{机器IP127.0.0.1 应用名称测试 失败阶段启动阶段 异常信息while scanning a simple key in reader, line 5, column 1: xxxxx ^ could not find expected : in reader, line 5, column 6: xxxxx ^ 上下文状态未创建 } 14:58:32.552 [main] ERROR org.springframework.boot.SpringApplication -- Application run failed org.yaml.snakeyaml.scanner.ScannerException: while scanning a simple key in reader, line 5, column 1: xxxxx ^ could not find expected : in reader, line 5, column 6: xxxxx ^生产价值覆盖所有启动失败场景包括上下文未创建的早期失败告警信息包含机器 IP、异常栈等生产排查关键信息缩短定位时间自动释放已初始化资源避免数据库连接、线程池等资源泄漏。四、场景 3环境配置校验提前拦截非法配置业务痛点生产环境中常因配置错误比如数据库地址写错、缺少核心环境变量导致应用启动后不可用问题发现滞后。解决方案在environmentPrepared阶段校验核心配置若配置不合法直接抛出异常终止启动流程避免 “启动成功但服务不可用” 的无效状态。核心实现packagecom.example.demo.listener;importorg.springframework.boot.ConfigurableBootstrapContext;importorg.springframework.boot.SpringApplicationRunListener;importorg.springframework.core.env.ConfigurableEnvironment;importjava.net.InetAddress;importjava.net.UnknownHostException;importjava.util.UUID;publicclassStartupEnvConfigValidListenerimplementsSpringApplicationRunListener{OverridepublicvoidenvironmentPrepared(ConfigurableBootstrapContextbootstrapContext,ConfigurableEnvironmentenvironment){// 1. 校验核心配置项生产环境可配置化校验规则validateCoreConfig(environment,spring.datasource.url);validateCoreConfig(environment,redis.host);validateCoreConfig(environment,app.prod.mode);// 2. 校验环境一致性比如生产环境禁止使用test profilevalidateProfile(environment);// 3. 补充生产环境必要配置比如动态注入机器IP到配置中try{supplementProdConfig(environment);}catch(UnknownHostExceptione){thrownewRuntimeException(e);}}// 校验核心配置是否存在且合法privatevoidvalidateCoreConfig(ConfigurableEnvironmentenvironment,StringconfigKey){Stringvalueenvironment.getProperty(configKey);if(valuenull||value.trim().isEmpty()){thrownewIllegalArgumentException(生产环境核心配置缺失configKey);}// 自定义规则校验比如数据库URL格式if(configKey.equals(spring.datasource.url)!value.startsWith(jdbc:mysql://)){thrownewIllegalArgumentException(生产环境数据库URL格式非法value);}}// 校验Profile合法性生产环境禁止test/devprivatevoidvalidateProfile(ConfigurableEnvironmentenvironment){String[]activeProfilesenvironment.getActiveProfiles();for(Stringprofile:activeProfiles){if(test.equals(profile)||dev.equals(profile)){thrownewIllegalStateException(生产环境禁止激活test/dev Profileprofile);}}}// 补充生产配置比如注入机器IP、应用实例IDprivatevoidsupplementProdConfig(ConfigurableEnvironmentenvironment)throwsUnknownHostException{environment.getSystemProperties().put(app.server.ip,InetAddress.getLocalHost());environment.getSystemProperties().put(app.instance.id,UUID.randomUUID().toString().substring(0,8));}}输出/Library/Java/JavaVirtualMachines/jdk-21.jdk/Contents/Home/bin/java -agentlib:jdwptransportdt_socket,address127.0.0.1:54360,suspendy,servern -javaagent:/Users/wangmingfei/Library/Caches/JetBrains/IdeaIC2024.3/captureAgent/debugger-agent.jar -Dkotlinx.coroutines.debug.enable.creation.stack.tracefalse -Ddebugger.agent.enable.coroutinestrue -Dkotlinx.coroutines.debug.enable.flows.stack.tracetrue -Dkotlinx.coroutines.debug.enable.mutable.state.flows.stack.tracetrue -Dfile.encodingUTF-8 -Dsun.stdout.encodingUTF-8 -Dsun.stderr.encodingUTF-8 -classpath /Users/wangmingfei/Documents/个人/05 java天梯之路/01 源码/03 每日打卡系列/daily-check-in/springboot钩子/demo/target/classes:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-starter-web/3.5.8/spring-boot-starter-web-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-starter/3.5.8/spring-boot-starter-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot/3.5.8/spring-boot-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-autoconfigure/3.5.8/spring-boot-autoconfigure-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-starter-logging/3.5.8/spring-boot-starter-logging-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/ch/qos/logback/logback-classic/1.5.21/logback-classic-1.5.21.jar:/usr/local/apache-maven-3.9.9/repository/ch/qos/logback/logback-core/1.5.21/logback-core-1.5.21.jar:/usr/local/apache-maven-3.9.9/repository/org/apache/logging/log4j/log4j-to-slf4j/2.24.3/log4j-to-slf4j-2.24.3.jar:/usr/local/apache-maven-3.9.9/repository/org/apache/logging/log4j/log4j-api/2.24.3/log4j-api-2.24.3.jar:/usr/local/apache-maven-3.9.9/repository/org/slf4j/jul-to-slf4j/2.0.17/jul-to-slf4j-2.0.17.jar:/usr/local/apache-maven-3.9.9/repository/jakarta/annotation/jakarta.annotation-api/2.1.1/jakarta.annotation-api-2.1.1.jar:/usr/local/apache-maven-3.9.9/repository/org/yaml/snakeyaml/2.4/snakeyaml-2.4.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-starter-json/3.5.8/spring-boot-starter-json-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/core/jackson-databind/2.19.4/jackson-databind-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/core/jackson-annotations/2.19.4/jackson-annotations-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/core/jackson-core/2.19.4/jackson-core-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.19.4/jackson-datatype-jdk8-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.19.4/jackson-datatype-jsr310-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.19.4/jackson-module-parameter-names-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-starter-tomcat/3.5.8/spring-boot-starter-tomcat-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/org/apache/tomcat/embed/tomcat-embed-core/10.1.49/tomcat-embed-core-10.1.49.jar:/usr/local/apache-maven-3.9.9/repository/org/apache/tomcat/embed/tomcat-embed-el/10.1.49/tomcat-embed-el-10.1.49.jar:/usr/local/apache-maven-3.9.9/repository/org/apache/tomcat/embed/tomcat-embed-websocket/10.1.49/tomcat-embed-websocket-10.1.49.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-web/6.2.14/spring-web-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-beans/6.2.14/spring-beans-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/io/micrometer/micrometer-observation/1.15.6/micrometer-observation-1.15.6.jar:/usr/local/apache-maven-3.9.9/repository/io/micrometer/micrometer-commons/1.15.6/micrometer-commons-1.15.6.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-webmvc/6.2.14/spring-webmvc-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-aop/6.2.14/spring-aop-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-context/6.2.14/spring-context-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-expression/6.2.14/spring-expression-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/slf4j/slf4j-api/2.0.17/slf4j-api-2.0.17.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-core/6.2.14/spring-core-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-jcl/6.2.14/spring-jcl-6.2.14.jar:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar com.example.demo.DemoApplication 已连接到地址为 127.0.0.1:54360传输: 套接字 的目标虚拟机 [2025-12-10T15:01:17.628507] 启动开始 - 应用启动流程初始化 starting - environmentPrepared 耗时131ms [2025-12-10T15:01:17.761066] 环境准备完成 - 配置文件/环境变量加载完毕当前环境 2025-12-10T15:01:17.86708:00 ERROR 7888 --- [demo] [ main] o.s.boot.SpringApplication : Application run failed java.lang.IllegalArgumentException: 生产环境核心配置缺失spring.datasource.url at com.example.demo.listener.StartupEnvConfigValidListener.validateCoreConfig(StartupEnvConfigValidListener.java:34) ~[classes/:na] at com.example.demo.listener.StartupEnvConfigValidListener.environmentPrepared(StartupEnvConfigValidListener.java:15) ~[classes/:na] at org.springframework.boot.SpringApplicationRunListeners.lambda$environmentPrepared$2(SpringApplicationRunListeners.java:64) ~[spring-boot-3.5.8.jar:3.5.8] at java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na] at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:118) ~[spring-boot-3.5.8.jar:3.5.8] at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:112) ~[spring-boot-3.5.8.jar:3.5.8] at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:63) ~[spring-boot-3.5.8.jar:3.5.8] at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:353) ~[spring-boot-3.5.8.jar:3.5.8] at org.springframework.boot.SpringApplication.run(SpringApplication.java:313) ~[spring-boot-3.5.8.jar:3.5.8] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) ~[spring-boot-3.5.8.jar:3.5.8] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) ~[spring-boot-3.5.8.jar:3.5.8] at com.example.demo.DemoApplication.main(DemoApplication.java:11) ~[classes/:na] 已与地址为 127.0.0.1:54360传输: 套接字 的目标虚拟机断开连接 进程已结束退出代码为 1生产价值提前拦截非法配置避免 “启动成功但服务不可用” 的无效状态统一校验规则避免因配置问题导致的生产故障动态补充生产环境必要配置减少配置文件维护成本。五、场景 4启动前初始化敏感资源安全管控业务痛点生产环境中部分敏感资源比如加密密钥、证书文件需要在应用启动早期加载且需做权限校验、防篡改校验。解决方案在starting阶段加载敏感资源做前置校验确保资源合法后再继续启动流程。packagecom.example.demo.listener;importcn.hutool.core.io.FileUtil;importcn.hutool.crypto.digest.MD5;importorg.springframework.boot.ConfigurableBootstrapContext;importorg.springframework.boot.SpringApplicationRunListener;importjava.io.File;importjava.time.LocalDateTime;publicclassStartupSensResPreloaderListenerimplementsSpringApplicationRunListener{Overridepublicvoidstarting(ConfigurableBootstrapContextbootstrapContext){logStage(敏感资源初始化,开始加载生产环境加密证书);// 1. 加载证书文件从安全目录读取非classpathFilecertFilenewFile(/opt/prod/cert/prod_rsa.crt);if(!certFile.exists()){thrownewIllegalStateException(生产环境证书文件缺失/opt/prod/cert/prod_rsa.crt);}// 2. 校验证书权限生产环境要求证书文件仅root可读写checkFilePermission(certFile);// 3. 校验证书完整性防篡改if(!checkCertIntegrity(certFile)){thrownewIllegalStateException(证书文件已被篡改终止启动);}// 4. 加载密钥到全局上下文System.out.println(加载密钥到全局上下文);logStage(敏感资源初始化完成,证书加载成功权限及完整性校验通过);}// 校验文件权限生产环境安全管控privatevoidcheckFilePermission(Filefile){// 简化实现生产环境需结合操作系统校验文件属主和权限if(file.canRead()file.canWrite()){thrownewIllegalStateException(证书文件权限过高仅允许只读);}}// 校验证书完整性比如MD5校验privatebooleancheckCertIntegrity(Filefile){StringprodMd5xxx;// 生产环境MD5值可从配置中心拉取StringfileMd5MD5.create().digestHex16(file);returnprodMd5.equals(fileMd5);}// 结构化日志输出生产环境建议用SLF4JprivatevoidlogStage(Stringstage,Stringdesc){System.out.printf([%s] %s - %s%n,LocalDateTime.now(),stage,desc);}}输出/Library/Java/JavaVirtualMachines/jdk-21.jdk/Contents/Home/bin/java -agentlib:jdwptransportdt_socket,address127.0.0.1:55025,suspendy,servern -javaagent:/Users/wangmingfei/Library/Caches/JetBrains/IdeaIC2024.3/captureAgent/debugger-agent.jar -Dkotlinx.coroutines.debug.enable.creation.stack.tracefalse -Ddebugger.agent.enable.coroutinestrue -Dkotlinx.coroutines.debug.enable.flows.stack.tracetrue -Dkotlinx.coroutines.debug.enable.mutable.state.flows.stack.tracetrue -Dfile.encodingUTF-8 -Dsun.stdout.encodingUTF-8 -Dsun.stderr.encodingUTF-8 -classpath /Users/wangmingfei/Documents/个人/05 java天梯之路/01 源码/03 每日打卡系列/daily-check-in/springboot钩子/demo/target/classes:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-starter-web/3.5.8/spring-boot-starter-web-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-starter/3.5.8/spring-boot-starter-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot/3.5.8/spring-boot-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-autoconfigure/3.5.8/spring-boot-autoconfigure-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-starter-logging/3.5.8/spring-boot-starter-logging-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/ch/qos/logback/logback-classic/1.5.21/logback-classic-1.5.21.jar:/usr/local/apache-maven-3.9.9/repository/ch/qos/logback/logback-core/1.5.21/logback-core-1.5.21.jar:/usr/local/apache-maven-3.9.9/repository/org/apache/logging/log4j/log4j-to-slf4j/2.24.3/log4j-to-slf4j-2.24.3.jar:/usr/local/apache-maven-3.9.9/repository/org/apache/logging/log4j/log4j-api/2.24.3/log4j-api-2.24.3.jar:/usr/local/apache-maven-3.9.9/repository/org/slf4j/jul-to-slf4j/2.0.17/jul-to-slf4j-2.0.17.jar:/usr/local/apache-maven-3.9.9/repository/jakarta/annotation/jakarta.annotation-api/2.1.1/jakarta.annotation-api-2.1.1.jar:/usr/local/apache-maven-3.9.9/repository/org/yaml/snakeyaml/2.4/snakeyaml-2.4.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-starter-json/3.5.8/spring-boot-starter-json-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/core/jackson-databind/2.19.4/jackson-databind-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/core/jackson-annotations/2.19.4/jackson-annotations-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/core/jackson-core/2.19.4/jackson-core-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.19.4/jackson-datatype-jdk8-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.19.4/jackson-datatype-jsr310-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.19.4/jackson-module-parameter-names-2.19.4.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/boot/spring-boot-starter-tomcat/3.5.8/spring-boot-starter-tomcat-3.5.8.jar:/usr/local/apache-maven-3.9.9/repository/org/apache/tomcat/embed/tomcat-embed-core/10.1.49/tomcat-embed-core-10.1.49.jar:/usr/local/apache-maven-3.9.9/repository/org/apache/tomcat/embed/tomcat-embed-el/10.1.49/tomcat-embed-el-10.1.49.jar:/usr/local/apache-maven-3.9.9/repository/org/apache/tomcat/embed/tomcat-embed-websocket/10.1.49/tomcat-embed-websocket-10.1.49.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-web/6.2.14/spring-web-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-beans/6.2.14/spring-beans-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/io/micrometer/micrometer-observation/1.15.6/micrometer-observation-1.15.6.jar:/usr/local/apache-maven-3.9.9/repository/io/micrometer/micrometer-commons/1.15.6/micrometer-commons-1.15.6.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-webmvc/6.2.14/spring-webmvc-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-aop/6.2.14/spring-aop-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-context/6.2.14/spring-context-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-expression/6.2.14/spring-expression-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/slf4j/slf4j-api/2.0.17/slf4j-api-2.0.17.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-core/6.2.14/spring-core-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/org/springframework/spring-jcl/6.2.14/spring-jcl-6.2.14.jar:/usr/local/apache-maven-3.9.9/repository/cn/hutool/hutool-all/5.8.28/hutool-all-5.8.28.jar:/Applications/IntelliJ IDEA CE.app/Contents/lib/idea_rt.jar com.example.demo.DemoApplication 已连接到地址为 127.0.0.1:55025传输: 套接字 的目标虚拟机 [2025-12-10T15:14:31.615386] 启动开始 - 应用启动流程初始化 [2025-12-10T15:14:31.616603] 敏感资源初始化 - 开始加载生产环境加密证书 Exception in thread main java.lang.IllegalStateException: 生产环境证书文件缺失/opt/prod/cert/prod_rsa.crt at com.example.demo.listener.StartupSensResPreloaderListener.starting(StartupSensResPreloaderListener.java:18) at org.springframework.boot.SpringApplicationRunListeners.lambda$starting$0(SpringApplicationRunListeners.java:54) at java.base/java.lang.Iterable.forEach(Iterable.java:75) at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:118) at org.springframework.boot.SpringApplicationRunListeners.starting(SpringApplicationRunListeners.java:54) at org.springframework.boot.SpringApplication.run(SpringApplication.java:310) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) at com.example.demo.DemoApplication.main(DemoApplication.java:11) 已与地址为 127.0.0.1:55025传输: 套接字 的目标虚拟机断开连接 进程已结束退出代码为 1生产价值敏感资源早期加载避免启动后期因资源缺失导致失败严格的权限和完整性校验符合生产环境安全规范资源加载失败直接终止启动避免安全风险。六、总结SpringApplicationRunListener.starting()是 Spring Boot 启动生命周期中最早可介入的扩展点。此时日志系统已就绪但配置未加载、容器未创建是执行底层、非业务、高优先级初始化逻辑的理想时机。starting()虽小却是构建高可用、可观测、安全可控的 Spring Boot 应用的第一道防线。关注我每天5分钟带你从 Java 小白变身编程高手 点赞 关注转发让更多小伙伴一起进步 私信SpringBoot钩子源码 获取源码