Spring的CommandLineRunner 和 ApplicationRunner
前言
通常,springboot项目我们在启动后需要预加载一些数据活启动某些任务。spring提供了两个接口CommandLineRunner
和ApplicationRunner
.
使用
ApplicationRunner
@Slf4j
@Component
public class AppRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments appArgs) throws Exception {
log.info(StrUtil.format("AppRunner args: {}", JSON.toJSONString(appArgs)));
}
}
CommandLineRunner
@Slf4j
@Component
public class CmdRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
log.info(StrUtil.format("CmdRunner args: {}", JSON.toJSONString(args)));
}
}
启动类
@SpringBootApplication
public class XxxApplication {
public static void main(String[] args) {
System.out.println(StrUtil.format("main args: {}", JSON.toJSONString(args)));
SpringApplication.run(XxxApplication.class, args);
}
}
这里在启动的Program arguements
添加参数
--test.arg1=arg1 --test.arg2=arg2 nonOptionArg1 nonOptionArg2
启动日志如下
main args: ["--test.arg1=arg1","--test.arg2=arg2","nonOptionArg1","nonOptionArg2"]
......
Undertow started on port(s) 1170 (http)
Started OpenapiApplication in 24.984 seconds (JVM running for 27.903)
AppRunner args: {"nonOptionArgs":["nonOptionArg1","nonOptionArg2"],"optionNames":["test.arg2","test.arg1"],"sourceArgs":["--test.arg1=arg1","--test.arg2=arg2","nonOptionArg1","nonOptionArg2"]}
CmdRunner args: ["--test.arg1=arg1","--test.arg2=arg2","nonOptionArg1","nonOptionArg2"]
我看可以i发现:ApplicationRunner
比 CommandLineRunner
要优先。
区别
两者实现的方法名称一样,但是参数不一样,两者均可以获取到app启动参数,使用方法也大致相同。
ApplicationRunner
封装了参数,可以更便捷的获取启动参数的key value等。
ApplicationRunner
比 CommandLineRunner
要优先。
源码分析
spring的启动流程
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);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
callRunners()方法
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
这里在启动runner的时候,先获取了所有的ApplicationRunner
和 CommandLineRunner
的bean,然后用AnnotationAwareOrderComparator
排序,最后fore循环启动,所以,默认情况下ApplicationRunner
是比 CommandLineRunner
启动要优先,但是我们可以利用@Order 或者Ordered接口来控制这些bean的执行顺序