文章目录
前言
相信熟悉使用SpringBoot开发的人一定没少用过ApplicationRunner(至少我是没少用),一般都是通过实现ApplicationRunner接口+覆写run方法来嵌入一些操作至springboot应用启动完成阶段。
但其实除了ApplicationRunner的实现,CommandLineRunner的实现跟ApplicationRunner是在同一阶段调用的,那么他们到底有什么不同呢?虽然通过一些简单的实验能得出结论,但既然来都来了,不妨跟进下源码瞧瞧。
SpringBoot Version:2.1.7
实验代码
以下给出部分实验代码:
FirstApplicationRunner.java
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(1)
public class FirstApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("\u001B[32m >>> startup first application runner<<<");
}
}
FirstCommandlineRunner.java
@Component
@Order(1)
public class FirstCommandlineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("\u001B[32m >>> startup first
command runner<<<");
}
}
SecondApplicationRunner.java
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(2)
public class SecondApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("\u001B[32m >>> startup second application runner<<<");
}
}
SecondCommandlineRunner.java
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(2)
public class SecondCommandlineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("\u001B[32m >>> startup second
command runner<<<");
}
}
部分输出结果:
>>> startup first application runner<<<
>>> startup first command runner<<<
>>> startup second application runner<<<
>>> startup second command runner<<<
如果将上述实验代码的@Order都去掉,顺序会发生神奇的调换:
>>> startup first application runner<<<
>>> startup second application runner<<<
>>> startup first command runner<<<
>>> startup second command runner<<<
- 不再是交替执行,而是ApplicationRunner优先。
跟进源码
开始跟进
直接跟进SpringApplication#run方法:
org.springframework.boot.SpringApplication#run(java.lang.String...)
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
// 跟进这里
callRunners(context, applicationArguments);
...(略)
跟进callRunners方法,另外,请上面的run方法里构造了ApplicationArguments,后面还会分析它:
org.springframework.boot.SpringApplication#callRunners
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
// 获取ApplicationRunner的实现
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
// 获取CommandLineRunner的实现
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
// 根据@Order的值排序
AnnotationAwareOrderComparator.sort(runners);
// 遍历两组实现,回调其run方法。
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
- 在没有用@Order设置顺序时,永远是优先获取ApplicationRunner的实现,故ApplicationRunner会被优先回调。
这里先跟进getBeansOfType,这个方法没什么特别之处,只是遇到了之前学过的东西,想回顾一下,不喜欢的可以跳过下面两段:
org.springframework.context.support.AbstractApplicationContext#getBeansOfType(java.lang.Class<T>)
@Override
public <T> Map<String, T> getBeansOfType(@Nullable Class<T> type) throws BeansException {
assertBeanFactoryActive();
return getBeanFactory().getBeansOfType(type);
}
跟进assertBeanFactoryActive方法:
org.springframework.context.support.AbstractApplicationContext#assertBeanFactoryActive
protected void assertBeanFactoryActive() {
if (!this.active.get()) {
if (this.closed.get()) {
throw new IllegalStateException(getDisplayName() + " has been closed already");
}
else {
throw new IllegalStateException(getDisplayName() + " has not been refreshed yet");
}
}
}
- 这里的active和closed,在介绍preparerefresh的时候说过。
好了,将视角重新返回callRunners方法,来直接看看两种callRunner的重载:
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
try {
(runner).run(args);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
}
}
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
try {
(runner).run(args.getSourceArgs());
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
}
}
可见代码就是普通的重载,我们可以来看看断点的调试结果,下面拿FirstApplicationRunner
做例子:
继续跟进
再确保代码确实是如我们所想的那般运行后,我们将注意力重新放回两个callRunner方法上,我们能看到两种实现主要区别在run方法上:
// application
(runner).run(args);
(runner).run(args.getSourceArgs());
那么args和args.getSourceArgs()到底有什么区别呢?可以说整个流程下来也就这两参数的不同就直接代表了ApplicationRunner和CommandLineRunner的不同了。为了解答这个问题,我们需要回到前面的代码。
ApplicationRunner和CommandLineRunner的区别
视角重新回到SpringApplication#run方法,找到如下一行代码(前面也特别提醒过了):
org.springframework.boot.SpringApplication#run(java.lang.String...)
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
- 前面callRunner方法传给run的args对象其实就是此处的applicationArguments。
跟进构造方法:
org.springframework.boot.DefaultApplicationArguments#DefaultApplicationArguments
public DefaultApplicationArguments(String[] args) {
Assert.notNull(args, "Args must not be null");
this.source = new Source(args);
this.args = args;
}
- CommandLineRunner#run仅仅传进了source。
这里我们应该关注Source到底是什么,持续跟进构造函数,直到来到如下代码:
org.springframework.core.env.SimpleCommandLinePropertySource#SimpleCommandLinePropertySource(java.lang.String...)
public SimpleCommandLinePropertySource(String... args) {
super(new SimpleCommandLineArgsParser().parse(args));
}
这里的parse方法就是关键,跟进它:
org.springframework.core.env.SimpleCommandLineArgsParser#parse
public CommandLineArgs parse(String... args) {
CommandLineArgs commandLineArgs = new CommandLineArgs();
for (String arg : args) {
if (arg.startsWith("--")) {
String optionText = arg.substring(2, arg.length());
String optionName;
String optionValue = null;
if (optionText.contains("=")) {
optionName = optionText.substring(0, optionText.indexOf('='));
optionValue = optionText.substring(optionText.indexOf('=')+1, optionText.length());
}
else {
optionName = optionText;
}
if (optionName.isEmpty() || (optionValue != null && optionValue.isEmpty())) {
throw new IllegalArgumentException("Invalid argument syntax: " + arg);
}
commandLineArgs.addOptionArg(optionName, optionValue);
}
else {
commandLineArgs.addNonOptionArg(arg);
}
}
return commandLineArgs;
}
- 这段代码的逻辑其实很简单,就是截取参数--后面的key=value。
如果你还不明白的话,下面通过修改一些参数+断点来直观感受下:
增加启动参数--number=7
:
断点至FirstCommandlineRunner#run
:
断点至FirstCommandlineRunner#run
:
小结
- 无论是ApplicationRunner还是CommandLineRunner的实现,都会在SpringApplication启动完成之后(running事件广播之前)被回调。
- 两种实现在没有设置@Order时,ApplicationRunner实现的回调有绝对的优先权(绝对优于CommandLineRunner)。
- ApplicationRunner相当于是CommandLineRunner的增强版。