1.ApplicationRunner

springBoot项目启动时,若想在启动之后直接执行某一段代码,就可以用 ApplicationRunner这个接口,并实现接口里面的run(ApplicationArguments args)方法,方法中写上自己的想要的代码逻辑。

springboot项目的启动流程如下

springcloud 启动必须启动其他几个组件吗 springcloud项目启动顺序_spring

@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 : 项目成功启动------------------");
    }

}

代码测试:

springcloud 启动必须启动其他几个组件吗 springcloud项目启动顺序_System_02

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);
    }
}