spring boot工程启动成功后会回调ApplicationRunner和CommandLineRunner接口的run方法,这两个接口主要有两点不同:
1、ApplicationRunner的run(ApplicationArguments args)接收一个ApplicationArguments类型的参数,ApplicationArguments是对原始程序参数的封装类。CommandLineRunner的run(String... args)接收原始的程序参数。
2、若未加上@Order注解定义排序值或排序值相同,ApplicationRunner比CommandLineRunner先运行。
1、新建4个类
//未定义@Order排序值
@Component
public class ApplicationRunnerOne implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("#######启动加载器--ApplicationRunnerOne。原始程序参数:"+args.getSourceArgs()
+"。test="+args.getOptionValues("test").toString());
}
}
//@Order排序值为2
@Order(2)
@Component
public class ApplicationRunnerTwo implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("#######启动加载器--ApplicationRunnerTwo");
}
}
//未定义@Order排序值
@Component
public class CommandLineRunner1 implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("#######启动加载器--CommandLineRunner1");
}
}
//@Order排序值为2
@Order(2)
@Component
public class CommandLineRunner2 implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("#######启动加载器--CommandLineRunner2");
}
}
2、添加程序参数 --test=value01
3、启动spring boot,控制台输出如下:
#######启动加载器--ApplicationRunnerTwo
#######启动加载器--CommandLineRunner2
#######启动加载器--ApplicationRunnerOne。原始程序参数:[Ljava.lang.String;@39fa8ad2。test=[value01]
#######启动加载器--CommandLineRunner1
ApplicationRunnerTwo、CommandLineRunner2的@Order排序值相同,ApplicationRunnerTwo比CommandLineRunner2先运行。没加上@Order排序值的后运行。
ApplicationRunner、CommandLineRunner的源码比较简单,只需要在实现类的run方法中打一个断点,程序调用栈就很清晰了。
调用栈不深,开始分析源码。
1、在 SpringApplication#run(java.lang.String...) 中,ApplicationStartedEvent事件发布、监听完成之后,便调用Runner。
// 源码位置 org.springframework.boot.SpringApplication#run(java.lang.String...)
// 仅列出主要代码
public ConfigurableApplicationContext run(String... args) {
// 使用程序参数创建ApplicationArguments对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
listeners.started(context);
// 在ApplicationStartedEvent事件之后执行Runners
callRunners(context, applicationArguments);
}
1.1 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); 源码
// 源码位置 org.springframework.boot.DefaultApplicationArguments#DefaultApplicationArguments
public DefaultApplicationArguments(String... args) {
Assert.notNull(args, "Args must not be null");
// 通过args创建this.source
this.source = new DefaultApplicationArguments.Source(args);
// 依旧将args原始值存储在this.args属性中
this.args = args;
}
1.1.2 对args的处理在 org.springframework.core.env.SimpleCommandLineArgsParser#parse(String... args) 方法中,主要逻辑是通过 -- 和 = 分隔程序参数。
1.2、callRunners(context, applicationArguments); 源码
// 源码位置 org.springframework.boot.SpringApplication#callRunners
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List
1.2.1 AnnotationAwareOrderComparator.sort(runners); 通过排序值对类进行排序,在 spring boot 2源码系列(一)- 系统初始化器ApplicationContextInitializer 博客中有讲过,本文不再重复。仅补充一点,@Order(2)注解的类比没添加@Order注解的类先运行,这是因为:没添加@Order注解,排序值是 Ordered.LOWEST_PRECEDENCE 最低优先级。Ordered.LOWEST_PRECEDENCE的数值是Integer.MAX_VALUE。数值越大,排序越靠后。
/**
* 源码位置org.springframework.core.OrderComparator#getOrder(java.lang.Object)
* 类上没有@Order注解或者没实现Ordered接口,返回 Ordered.LOWEST_PRECEDENCE 排序值
*/
protected int getOrder(@Nullable Object obj) {
if (obj != null) {
//
Integer order = findOrder(obj);
if (order != null) {
return order;
}
}
return Ordered.LOWEST_PRECEDENCE;
}
1.2.2 callRunner((ApplicationRunner) runner, args); 和 callRunner((CommandLineRunner) runner, args); 源码
// callRunner((ApplicationRunner) runner, args);
// 源码位置 org.springframework.boot.SpringApplication#callRunner(org.springframework.boot.ApplicationRunner, org.springframework.boot.ApplicationArguments)
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
try {
// 调用ApplicationRunner实现类的run方法
(runner).run(args);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
}
}
// callRunner((CommandLineRunner) runner, args);
// 源码位置 org.springframework.boot.SpringApplication.callRunner(org.springframework.boot.CommandLineRunner, org.springframework.boot.ApplicationArguments)
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
try {
/**
* 通过 args.getSourceArgs() 获取原始程序参数
* 调用CommandLineRunner实现类run方法
*/
(runner).run(args.getSourceArgs());
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
}
}