在基于SpringBoot的项目开发中会遇到这样需求:在项目启动的时候需要做一些初始化的操作,比如初始化线程池,提前加载好加密证书等。那么可以考虑使用Spring提供的两个初始化器Runner:ApplicationRunner和CommandLineRunner。实现这两个接口的 Component
会在所有 Spring Beans
都初始化之后同时SpringApplication.run()
之前执行,非常适合在应用程序启动之初进行一些数据初始化的工作。在Spring的官方文档中介绍如下:
一、ApplicationRunner接口
该接口的方法会在服务启动之后被立即执行,主要用来做一些初始化的工作,但是该方法的运行是在SpringApplication.run(…) 执行完毕之前执行:
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class MyApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("Success to init use ApplicationRunner before startup!");
}
}
二、CommandLineRunner接口
CommandLineRunner跟ApplicationRunner在服务启动之后被立即执行,主要用来做一些初始化的工作,但是该方法的运行是在SpringApplication.run(…) 执行完毕之前执行,不同的是,CommandLineRunner接口中run方法的参数为String数组,ApplicationRunner中run方法的参数为ApplicationArguments。
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
@Slf4j
public class MyCommandLineRunner implements CommandLineRunner{
@Override
public void run(String... args) throws Exception {
log.info("Success to init use CommandLineRunnerbefore startup!");
}
}
三、Runner们
的执行顺序问题
如果我们在启动容器的时候需要初始化很多资源,并且初始化资源相互之间有序,那如何保证不同的 CommandLineRunner
的执行顺序呢?Spring Boot 也给出了解决方案。那就是使用 @Order
注解。
例如,项目中有两个 CommandLineRunner
的实现类:
@Component
@Order(1)
public class OrderRunner1 implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("The OrderRunner1 start to initialize ...");
}
}
@Component
@Order(2)
public class OrderRunner2 implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("The OrderRunner2 start to initialize ...");
}
}
通过测试会发现:添加@Order 注解的实现类会按照@Order注解内的整数顺序执行,并且@Order里面的值越小启动越早。
四、在Runner们
的run方法中注意事项
对于下列几种情况,会直接影响主程序的正常启动:
1、run()函数体内不能是死循环,即如下的情况:
@Component
public class InitializeRunnerService implements CommandLineRunner {
public void run(String... strings){
while(true){
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2、run()函数体内抛出未处理的异常,即如下的情况:
@Component
public class InitializeRunnerService implements CommandLineRunner {
public void run(String... strings){
throw new RuntimeException();
}
}
五、Runner们在SpringBoot中调用的源码分析
1、首先看SpringApplication.run()入口函数:
可以看出这些Runner是在SpringBoot完成启动后且在run()函数返回前调用的。callRunners()函数源码如下:
源码中可以看到使用了AnnotationAwareOrderComparator.sort(runners)对使用@Order注解的Runner进行排序后进行分ApplicationRunner和CommandLineRunner进行调用响应的run()方法的。