SpringBoot中CommandLineRunner和ApplicationRunner接口解析和使用
应用及场景
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); } }
通过以上代码,我们也就了解到这两个接口的实现类的执行时机了。
关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台
除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接