应用及场景

SpringBoot中提供了两个接口可以在Spring Boot启动的过程中进行一些额外的操作,比如读取配置文件、数据库操作等自定义的内容。

而这些功能的实现也非常简单,直接实现这两个接口并实现其run方法,然后将该类实例化即可。以下代码便实现了CommandLineRunner接口,并在run方法内打印了对应的日志,同时,通过@Component将其注册为Spring的一个bean。

@Component
public class LearnCommandLineRunner implements CommandLineRunner {

    @Override
    public void run(String... args) {
        System.out.println("LearnCommandLineRunner running");
    }
}

源代码

下面看一下CommandLineRunner和ApplicationRunner的源代码:

public interface CommandLineRunner {

    /**
     * Callback used to run the bean.
     * @param args incoming main method arguments
     * @throws Exception on error
     */
    void run(String... args) throws Exception;

}
public interface ApplicationRunner {

    /**
     * Callback used to run the bean.
     * @param args incoming application arguments
     * @throws Exception on error
     */
    void run(ApplicationArguments args) throws Exception;

}

通过源代码的对比会发现,它们唯一不同便是run方法的参数。在实际应用中它们的区别也只有这些。

执行顺序

通过接口的官方文档,我们得知其实执行CommandLineRunner和ApplicationRunner的实现类是有顺序的,只不过在示例中并没有展示。针对上面的示例,我们可以通过@Order或实现Ordered接口来对其指定执行顺序。

@Order(value = 1)
@Component
public class LearnCommandLineRunner implements CommandLineRunner {

    @Override
    public void run(String... args) {
        System.out.println("LearnCommandLineRunner running");
    }
}

使用源码分析

说到执行顺序,那么再进一步了解一下这两个方法是在什么时候执行的。这两个接口的实现执行的时机在于SpringApplication初始化之后,调用的run方法中被调用的。

public ConfigurableApplicationContext run(String... args) {
        // 创建 StopWatch 对象,用于统计 run 方法启动时长。
        StopWatch stopWatch = new StopWatch();
        // 启动统计。
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        // 配置 headless 属性。
        configureHeadlessProperty();
        // 获得 SpringApplicationRunListener 数组,
        // 该数组封装于 SpringApplicationRunListeners 对象的 listeners 中。
        SpringApplicationRunListeners listeners = getRunListeners(args);
        // 启动监听,遍历 SpringApplicationRunListener 数组每个元素,并执行。
        listeners.starting();
        try {
            //创建 ApplicationArguments 对象
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            // 加载属性配置,包括所有的配置属性(如:application.properties 中和外部的属性配置)
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            configureIgnoreBeanInfo(environment);
            // 打印 Banner
            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);
            // 调用 ApplicationRunner 和 CommandLineRunner 的运行方法。
            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;
    }

我们可以看到,在try方法的最后,会执行一个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);
            }
        }
    }

    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);
        }
    }

通过以上代码,我们也就了解到这两个接口的实现类的执行时机了。



SpringBoot中CommandLineRunner和ApplicationRunner接口解析和使用插图

关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台

除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接

本文链接:https://www.choupangxia.com/2019/09/25/springboot%e4%b8%adcommandlinerunner%e5%92%8capplicationrunner%e6%8e%a5%e5%8f%a3%e8%a7%a3%e6%9e%90%e5%92%8c%e4%bd%bf%e7%94%a8/