如果你想要使用SpringBoot
构建的项目在启动后运行一些特定的代码,那么CommandLineRunner
、ApplicationRunner
都是很好的选择。
推荐阅读
使用方式
我们以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
接口的使用方式也是一样的。
两者的区别?
从源码上分析,CommandLineRunner
与ApplicationRunner
两者之间只有#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
什么时候会被调用?
我们已经了解CommandLineRunner
与ApplicationRunner
两个接口的使用以及区别,是不是很想知道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()
方法的调用确实是在应用程序启动完成后,而且把ApplicationContext
与ApplicationArguments
对象都作为参数进行了传递,那么我们来看看这个方法究竟干了些什么事情?
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
中获取CommandLineRunner
、ApplicationRunner
接口实现类的实例,然后根据不同类型的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);
}
}
设置执行顺序
那如果我们创建了多个CommandLineRunner
、ApplicationRunner
实现类,还想要实现类在执行的时候有一定的先后顺序,那你不妨试下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;
}
}
接口与 注解的方式选择其中一种就可以了。