SpringBoot2.x基础篇:使用CommandLineRunner或ApplicationRunner

简介: 如果你想要使用`SpringBoot`构建的项目在启动后运行一些特定的代码,那么`CommandLineRunner`、`ApplicationRunner`都是很好的选择。

如果你想要使用SpringBoot构建的项目在启动后运行一些特定的代码,那么CommandLineRunnerApplicationRunner都是很好的选择。

推荐阅读

使用方式

我们以CommandLineRunner创建了一个简单的例子,如下所示:

/**
 * {@link CommandLineRunner}接口使用示例
 *
 * @author 恒宇少年
 */
@Component
public class CommandLineRunnerExample implements CommandLineRunner {
    /**
     * 实例化本类的日志采集器
     */
    static LoggingCollector logging = LoggingCollectorFactory.getCollector(CommandLineRunnerExample.class);

    @Override
    public void run(String... args) throws Exception {
        // 执行特定的代码
        logging.debug("main方法参数列表:{}", args);
    }
}

CommandLineRunner接口的定义很简单,只提供了一个名为#run()的方法,我们只需要实现该方法做一些自定义的业务逻辑即可,ApplicationRunner接口的使用方式也是一样的。

两者的区别?

从源码上分析,CommandLineRunnerApplicationRunner两者之间只有#run()方法的参数不一样而已。

CommandLineRunner:

@FunctionalInterface
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;

}

ApplicationRunner:

@FunctionalInterface
public interface ApplicationRunner {

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

}

CommandLineRunner#run()方法的参数是启动SpringBoot应用程序main方法的参数列表,而ApplicationRunner#run()方法的参数则是ApplicationArguments对象。

在之前的文章中也提到过ApplicatgionArguments对象,并使用它获取外部的配置参数,查看:应用程序在启动时访问启动项参数

建议:如果你在项目启动时需要获取类似 "--xxx" 的启动参数值建议使用 ApplicationRunner

什么时候会被调用?

我们已经了解CommandLineRunnerApplicationRunner两个接口的使用以及区别,是不是很想知道SpringBoot在启动时在什么时候调用它们的呢?

我们大家都知道SpringBoot应用程序的启动主要归功于SpringApplication这个类,我们在创建项目时在启动类内会调用SpringApplication#run()方法,如下所示:

public static void main(String[] args) {
  SpringApplication.run(LoggingServiceApplication.class, args);
}

那我们来查看下SpringApplication#run()方法的源码,根据查看方法之间的相互调用,最终我们会定位到org.springframework.boot.SpringApplication#run(java.lang.String...)这个方法,阅读该方法时发现有关调用Runner的定义,如下所示:

// 省略部分源码
listeners.started(context);
callRunners(context, applicationArguments);
// 省略部分源码

#callRunnners()方法的调用确实是在应用程序启动完成后,而且把ApplicationContextApplicationArguments对象都作为参数进行了传递,那么我们来看看这个方法究竟干了些什么事情?

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

我想大家看到这里就应该明白了,这个方法就是在执行CommandLineRunner以及ApplicationRunner实现类实例的#run()方法,首先会从ApplicationContext中获取CommandLineRunnerApplicationRunner接口实现类的实例,然后根据不同类型的Runner实例去调用了callRunner方法。

SpringApplication#callRunner:


// 调用ApplicationRunner实现类实例#run()
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
  try {
    (runner).run(args);
  }
  catch (Exception ex) {
    throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
  }
}
// 调用CommandLineRunner实现类实例#run()
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
  try {
    (runner).run(args.getSourceArgs());
  }
  catch (Exception ex) {
    throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
  }
}

设置执行顺序

那如果我们创建了多个CommandLineRunnerApplicationRunner实现类,还想要实现类在执行的时候有一定的先后顺序,那你不妨试下org.springframework.core.annotation.Order这个注解或者实现org.springframework.core.Ordered接口。

CommandLineRunnerExample:

/**
 * {@link CommandLineRunner}接口使用示例
 *
 * @author 恒宇少年
 */
@Component
@Order(100)
public class CommandLineRunnerExample implements CommandLineRunner, Ordered {
    // 省略部分代码
    @Override
    public int getOrder() {
        return 100;
    }
}
接口注解的方式选择其中一种就可以了。
相关文章
|
3月前
|
安全 Java Spring
SpringBoot2 | SpringBoot监听器源码分析 | 自定义ApplicationListener(六)
SpringBoot2 | SpringBoot监听器源码分析 | 自定义ApplicationListener(六)
49 0
|
Java Spring
Spring Boot CommandLineRunner接口详解
Spring Boot CommandLineRunner接口详解
Spring Boot CommandLineRunner接口详解
|
3天前
|
XML 监控 Java
Spring基础 SpringAOP
Spring基础 SpringAOP
9 0
|
4月前
|
Java
Springboot 之 HandlerMethodArgumentResolver 运用
Springboot 之 HandlerMethodArgumentResolver 运用
36 0
|
5月前
|
Java
SpringBoot:第六篇 CommandLineRunner
SpringBoot:第六篇 CommandLineRunner
21 0
|
Java Spring 容器
Spring Boot - ApplicationRunner && CommandLineRunner扩展接口
ApplicationRunner && CommandLineRunner扩展接口
219 0
Spring Boot - ApplicationRunner && CommandLineRunner扩展接口
|
XML Java 数据格式
SpringBoot源码学习(三) BeanFactory 与 ApplicationContext
### 前言 + [上文](https://www.atatech.org/articles/145056) 已经分析了springboot启动的前半部分, 下面就要还是分析真正核心的spring容器的整个生命周期了, 但是在讲之前还是要把一些基本概念讲清楚。 + 因为这部分的文章在网络上实在是太多了, 随便搜一下就能搜到一脸盆 所以本文只会对我对这部分的认知做一些梳理和归纳 ##
87 0
SpringBoot源码学习(三) BeanFactory 与 ApplicationContext
|
XML SpringCloudAlibaba 前端开发
spring InitializingBean 接口都不知道,源码还是缓缓吧
spring InitializingBean 接口都不知道,源码还是缓缓吧
173 0
spring InitializingBean 接口都不知道,源码还是缓缓吧
|
Java 程序员 网络安全
SpringBoot应用使用自定义的ApplicationContext实现类
在学习spring容器初始化的过程中,发现spring容器预留了一些扩展点,我们可以写子类来做功能扩展,今天就来探寻SpringBoot框架下的扩展方式
267 0
SpringBoot应用使用自定义的ApplicationContext实现类
|
前端开发 Java 应用服务中间件
深入学习Springboot的注解@SpringBootApplication
深入学习Springboot的注解@SpringBootApplication
138 0
深入学习Springboot的注解@SpringBootApplication