十、ApplicationRunner、CommandLineRunner 接口
ApplicationRunner和CommandLineRunner 是SpringBoot提供的接口,实现这两个接口中任意一个接口,并且配置给spring容器管理的bean,重写这两个接口的方法,将会被回调。
public class SpringApplication {
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 使用spi机制加载SpringApplicationRunListener.class, 获取这个类的实例
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 封装请求参数
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 打印banner
Banner printedBanner = printBanner(environment);
// 创建ApplicationContext对象, 此过程中完成类型推断
context = createApplicationContext();
// 使用spi机制加载SpringBootExceptionReporter.class, 获取这个类的实例
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新ApplicationContext, 即刷新spring容器
refreshContext(context);
// 空实现, 受保护的方法
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(),
stopWatch);
}
// 回调所有listener的started方法,
// 这个listener就是前面提到的由spi机制加载的SpringApplicationRunListeners的实例
listeners.started(context);
// 【在此处, 回调spring容器中所有的runner】
// 【即Spring容器中任何实现了ApplicationRunner和CommandLineRunner这两个接口的bean】
// 【将会在此处回调它们重写的这两个接口的方法】
// 下面进去看看这个方法, 注意个细节context传进去了
callRunners(context, applicationArguments);// <---------------回调方法,进去看看
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 回调所有listener的running方法,
// 这个listener就是前面提到的由spi机制加载的SpringApplicationRunListeners的实例
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
}
// 同样是在SpringApplication的这个类中,调用的方法
public class SpringApplication {
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
// 到这里来就已经很明显了, 它从传过来的context,
// 即从spring容器中拿到了实现ApplicationRunner接口的bean
// 和实现CommandLineRunner接口的bean
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
// 很好, 这里来了个排序,
// 这个排序可以使用@Order注解的value值指定, 值越大,优先级越低。默认是Ordered.LOWEST_PRECEDENCE[最低优先级]
// 具体可以看org.springframework.core.Ordered这个接口
// @Order注解仅仅用于控制组件的加载顺序,不能控制注入优先级
// 或者由@Priority注解的value值来指定, 值通常不设置为负数 , 值越小, 优先级越高
// 可以用来处理同一个接口下有多个实现的bean, 那么优先注入哪个bean可以用Priority指定,效果有点像@Primary
// 可以控制注入的优先级
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);//<<-----------回调所有ApplicationRunner接口
// 实现bean重写的run方法
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);//<<-----------回调所有CommandLineRunner接口
// 实现bean重写的run方法
}
}
}
// 回调就在下面, 这两个接口唯一差别就是回调的顺序, ApplicationRunner先, CommandLineRunner靠后
// 其实还有一个问题指出一下, 在 refreshContext(context);这步, 就相当于已经把我们自己配置的bean实例化放到容器中了
// 所以到callRunners(context, applicationArguments);这步, 从容器中拿到接口的实现bean是没有问题的
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);
}
}
}