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是上下文环境/容器;
ApplicationEvent是抽象类,里面只有一个构造函数和一个长整型的timestamp;
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);
}
}