Springcloud学习笔记48--ApplicationRunner、CommandLineRunner、ApplicationListener接口使用(springboot项目启动后自动执行)
1.ApplicationRunner
springBoot项目启动时,若想在启动之后直接执行某一段代码,就可以用 ApplicationRunner这个接口,并实现接口里面的run(ApplicationArguments args)方法,方法中写上自己的想要的代码逻辑。
springboot项目的启动流程如下
@Component //此类一定要交给spring管理 public class ConsumerRunner implements ApplicationRunner{ @Override public void run(ApplicationArgumers args) throws Exception{ //代码 System.out.println("需要在springBoot项目启动时执行的代码---"); } }
若有多个代码段需要执行,可用@Order注解设置执行的顺序。
@Component //此类一定要交给spring管理 @Order(value=1) //首先执行 public class ConsumerRunnerA implements ApplicationRunner{ @Override public void run(ApplicationArgumers args) throws Exception{ //代码 System.out.println("需要在springBoot项目启动时执行的代码1---"); } }
@Component //此类一定要交给spring管理 @Order(value=2) //其次执行 public class ConsumerRunnerB implements ApplicationRunner{ @Override public void run(ApplicationArgumers args) throws Exception{ //代码 System.out.println("需要在springBoot项目启动时执行的代码2---"); } }
注意:在使用ApplicationRunner或者CommandLineRunner时如果报错或者出现超时的情况会导致整个程序崩溃。
解决办法
在使用时另外开一个线程
@Component public class ApplicationRunnerImpl implements ApplicationRunner { @Override public void run(ApplicationArguments args) throws Exception { new Thread(()->{ //是否创建二维码、导入Excel临时存放路径 File tempFiles = new File(CorsConfig.tempPath); if(!tempFiles.exists()){//如果文件夹不存在 tempFiles.mkdirs();//创建文件夹 System.out.println("创建临时路径:"+tempFiles); }else { System.out.println("临时路径:"+tempFiles); } //唤醒mq SpringUtil.getBean(TesSendAndGet.class); }).start(); } }
在SpringApplication
类的callRunners
方法中,我们能看到这两个接口的具体调用
private void callRunners(ApplicationContext context, ApplicationArguments args) { List runners = new ArrayList(); runners.addAll(context.getBeansOfType(ApplicationRunner.class).values()); runners.addAll(context.getBeansOfType(CommandLineRunner.class).values()); AnnotationAwareOrderComparator.sort(runners); //如果多个类实现了相关Runner接口,可以用@order来控制先后执行顺序 for (Iterator localIterator = new LinkedHashSet(runners).iterator(); localIterator.hasNext(); ) { Object runner = localIterator.next(); if ((runner instanceof ApplicationRunner)) { callRunner((ApplicationRunner)runner, args); } if ((runner instanceof CommandLineRunner)) callRunner((CommandLineRunner)runner, args); } }
2. CommandLineRunner接口使用
CommandLineRunner、ApplicationRunner 接口是在容器启动成功后的最后一步回调(类似开机自启动)。
- 写一个类,实现CommandLineRunner接口,将该类注入到Spring容器中;
- 可以通过@Order注解(属性指定数字越小表示优先级越高)或者Ordered接口来控制执行顺序。
例如,我们自定义两个类,实现CommandLineRunner接口,实现run方法,在run方法中添加处理逻辑。
package com.ttbank.flep.core.listener; import org.springframework.boot.CommandLineRunner; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** * @Author lucky * @Date 2022/7/7 16:47 */ @Order(5) @Component public class AppStartReport implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("AppStartReport : 项目成功启动------------------"); } }
package com.ttbank.flep.core.listener; import org.springframework.boot.CommandLineRunner; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; /** * @Author lucky * @Date 2022/7/7 16:53 */ @Order(2) @Component public class AppStartReport2 implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("AppStartReport2 : 项目成功启动------------------"); } }
代码测试:
3. ApplicationListener
ApplicationListener是Spring事件机制的一部分,与抽象类ApplicationEvent类配合来完成ApplicationContext的事件机制。
ApplicationContext是应用的容器,是spring的核心,Context是上下文环境/容器;
ApplicationListener是一个接口,里面只有一个onApplicationEvent方法。 所以自己的类在实现该接口的时候,要实装该方法。
Spring Boot 启动事件顺序
(1) ApplicationStartingEvent
这个事件在 Spring Boot 应用运行开始时,且进行任何处理之前发送(除了监听器和初始化器注册之外)。
(2) ApplicationEnvironmentPreparedEvent
这个事件在当已知要在上下文中使用 Spring 环境(Environment)时,在 Spring 上下文(context)创建之前发送。
(3) ApplicationContextInitializedEvent
这个事件在当 Spring 应用上下文(ApplicationContext)准备好了,并且应用初始化器(ApplicationContextInitializers)已经被调用,在 bean 的定义(bean definitions)被加载之前发送。
(4) ApplicationPreparedEvent
这个事件是在 Spring 上下文(context)刷新之前,且在 bean 的定义(bean definitions)被加载之后发送。
(5) ApplicationStartedEvent
这个事件是在 Spring 上下文(context)刷新之后,且在 application/ command-line runners 被调用之前发送。
(6) AvailabilityChangeEvent
这个事件紧随上个事件之后发送,状态:ReadinessState.CORRECT,表示应用已处于活动状态。
(7) ApplicationReadyEvent
这个事件在任何 application/ command-line runners 调用之后发送。
(8) AvailabilityChangeEvent
这个事件紧随上个事件之后发送,状态:ReadinessState.ACCEPTING_TRAFFIC,表示应用可以开始准备接收请求了。
(9) ApplicationFailedEvent
这个事件在应用启动异常时进行发送。
注意:上面所介绍的这些事件列表仅包括绑定到 SpringApplication 的 SpringApplicationEvents 事件,除了这些事件以外,以下事件也会在 ApplicationPreparedEvent 之后和 ApplicationStartedEvent 之前发送:
<1>WebServerInitializedEvent
这个 Web 服务器初始化事件在 WebServer 启动之后发送,对应的还有 ServletWebServerInitializedEvent(Servlet Web 服务器初始化事件)、ReactiveWebServerInitializedEvent(响应式 Web 服务器初始化事件)。
<2>ContextRefreshedEvent
这个上下文刷新事件是在 Spring 应用上下文(ApplicationContext)刷新之后发送。
3.1 ApplicationReadyEventListener监听器
package com.didispace; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationListener; @Slf4j public class ApplicationReadyEventListener implements ApplicationListener<ApplicationReadyEvent> { @Override public void onApplicationEvent(ApplicationReadyEvent event) { log.info("......ApplicationReadyEvent......"); } }
3.2 ApplicationStartedEventListener监听器
package com.didispace; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.event.ApplicationStartedEvent; import org.springframework.context.ApplicationListener; @Slf4j public class ApplicationStartedEventListener implements ApplicationListener<ApplicationStartedEvent> { @Override public void onApplicationEvent(ApplicationStartedEvent event) { log.info("......ApplicationStartedEvent......"); } }
官方文档对ApplicationStartedEvent和ApplicationReadyEvent的解释:
An ApplicationStartedEvent is sent after the context has been refreshed but before any application and command-line runners have been called.An ApplicationReadyEvent is sent after any application and command-line runners have been called. It indicates that the application is ready to service requests
从文档中我们可以知道他们两中间还有一个过程就是command-line runners被调用的内容。所以,为了更准确的感受这两个事件的区别,我们在应用主类中加入CommandLineRunner的实现。
2018-10-24 19:32:01.772 INFO 14708 --- [ main] c.d.ApplicationPreparedEventListener : ......ApplicationPreparedEvent...... ...... 2018-10-24 19:32:05.032 INFO 14708 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup 2018-10-24 19:32:05.068 INFO 14708 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2018-10-24 19:32:05.072 INFO 14708 --- [ main] com.didispace.Application : Started Application in 3.8 seconds (JVM running for 4.372) 2018-10-24 19:32:05.074 INFO 14708 --- [ main] c.d.ApplicationStartedEventListener : ......ApplicationStartedEvent...... 2018-10-24 19:32:05.075 INFO 14708 --- [ main] com.didispace.Application$DataLoader : Loading data... 2018-10-24 19:32:05.076 INFO 14708 --- [ main] c.d.ApplicationReadyEventListener : ......ApplicationReadyEvent......
3.3 Spring Boot程序获取tomcat启动端口
有时我们需要Spring Boot程序的启动端口,实现ApplicationListener接口,通过WebServerInitializedEvent(Spring Boot 2.0.x版本)
@Component @Slf4j public class ServerConfig implements ApplicationListener<WebServerInitializedEvent> { private int serverPort; public int getPort() { return this.serverPort; } @Override public void onApplicationEvent(WebServerInitializedEvent event) { this.serverPort = event.getWebServer().getPort(); log.info("Get WebServer port {}", serverPort); } }
参考文献:
https://blog.csdn.net/weixin_41667076/article/details/121701303
https://blog.csdn.net/m0_60215634/article/details/122457170
https://blog.csdn.net/qq_28165595/article/details/113197826(重点参考)
https://blog.csdn.net/chengqiuming/article/details/83349486(ApplicationListener类)
https://blog.csdn.net/qq_42948241/article/details/114587189(Spring Boot 启动事件顺序)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)