前言

通常,springboot项目我们在启动后需要预加载一些数据活启动某些任务。spring提供了两个接口CommandLineRunnerApplicationRunner.

使用

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发现:ApplicationRunnerCommandLineRunner 要优先。

区别

两者实现的方法名称一样,但是参数不一样,两者均可以获取到app启动参数,使用方法也大致相同。

ApplicationRunner封装了参数,可以更便捷的获取启动参数的key value等。

ApplicationRunnerCommandLineRunner 要优先。

源码分析

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的时候,先获取了所有的ApplicationRunnerCommandLineRunner 的bean,然后用AnnotationAwareOrderComparator排序,最后fore循环启动,所以,默认情况下ApplicationRunner 是比 CommandLineRunner 启动要优先,但是我们可以利用@Order 或者Ordered接口来控制这些bean的执行顺序