上篇文章介绍完SpringBoot内置tomcat的启动流程后,SpringBoot在Spring容器启动过程所做的扩展就大体介绍完了,本节继续SpringBoot的run方法,介绍一个比较有用的扩展点:ApplicationRunner

当然,在内置tomcat启动完成,到这行callRunners被调用,中间还经历了一些事件的发布,比如ApplicationStartedEvent,我们之前已经介绍过事件发布的原理,并详细分析了好几个事件了,它们发布的流程都是一致的,相信看到这里的朋友,已经很熟悉SpringBoot的事件发布机制,可以尝试自己去分析这几个事件的流程了
首先来看下ApplicationRunner接口的定义
@FunctionalInterface
public interface ApplicationRunner {
void run(ApplicationArguments args) throws Exception;
}
它是一个函数式接口,只有一个run方法,实现这个接口后,在Spring容器启动完成之后,会回调run方法中的逻辑,我们看下效果
@SpringBootApplication
public class Application implements ApplicationRunner {
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("系统启动完成...");
}
}
让SpringBoot的启动类实现该接口,在run方法中打印一行日志,启动项目就可以看到控制台里,tomcat启动成功之后打印了这行日志 
接下来进入正题,看下callRunners方法的逻辑,需注意执行到这里,Spring容器的初始化流程已经完全结束了,它的第一个参数就是启动好的Spring容器,而第二个参数ApplicationArguments我们之前分析过,是对项目的启动参数String[] args的封装
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);
Iterator var4 = (new LinkedHashSet(runners)).iterator();
while(var4.hasNext()) {
Object runner = var4.next();
if (runner instanceof ApplicationRunner) {
this.callRunner((ApplicationRunner)runner, args);
}
if (runner instanceof CommandLineRunner) {
this.callRunner((CommandLineRunner)runner, args);
}
}
}
首先从容器里获取所有实现了ApplicationRunner接口和CommandLineRunner接口的bean,其中CommandLineRunner接口的定义跟ApplicationRunner类似
@FunctionalInterface
public interface CommandLineRunner {
void run(String... args) throws Exception;
}
它们都只有一个run方法,只是这里CommandLineRunner接收的参数是String[] args,就是原始的启动参数,而ApplicationRunner接收的是我们传进来的封装过的ApplicationArguments
public interface ApplicationArguments {
String[] getSourceArgs();
Set<String> getOptionNames();
boolean containsOption(String name);
List<String> getOptionValues(String name);
List<String> getNonOptionArgs();
}
其中的sourceArgs就是原始的String[] args,下面的各种option是对启动参数做了一个解析,比如- -port=8090等
在ApplicationRunner的实际使用中,很少会真的用到这些启动参数,大多数情况下,我们需要利用的是它的调用时机,所以在实际工作中,使用ApplicationRunner和使用CommandLineRunner区别不大
回到callRunners方法,从容器中找到了这些接口的实现类后,将它们存在同一个List中做了一个排序,所以这里也可以看到,ApplicationRunner和CommandLineRunner执行顺序并没有严格的前后之分,依赖其自身实现的排序接口
sort之后依次调了callRunner方法
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
try {
runner.run(args);
} catch (Exception var4) {
throw new IllegalStateException("Failed to execute ApplicationRunner", var4);
}
}
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
try {
runner.run(args.getSourceArgs());
} catch (Exception var4) {
throw new IllegalStateException("Failed to execute CommandLineRunner", var4);
}
}
两个callRunner方法都接收ApplicationArguments ,只是在commandLineRunner的实现中,调用run的时候从入参取出了原始的String[] args,最终调用了我们自己实现接口时定义的逻辑
这个扩展点比较简单,但却非常实用,我们经常会需要项目启动的时候做一些跟容器无关的初始化,比如加载一些外部网络资源等,就可以依赖这个接口实现
|