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的执行顺序