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