CommandLineRunner与ApplicationRunner的使用及源码解析

 

    我们在使用SpringBoot搭建项目的时候,如果希望在项目启动完成之前,能够初始化一些操作,针对这种需求,可以考虑实现如下两个接口(任一个都可以)

 
  1. org.springframework.boot.CommandLineRunner

  2. org.springframework.boot.ApplicationRunner

1.CommandLineRunner接口的使用

 
  1. @Component
    
    public class TestCommandLineRunner implements CommandLineRunner {
    
    
    @Override
    
    // 实现该接口之后,实现其run方法,可以在run方法中自定义实现任务
    
    public void run(String... args) throws Exception {
    
    System.out.println("服务启动,TestCommandLineRunner执行启动加载任务...");
    
    if(null != args){
    
    for (String s : args) {
    
    System.out.println(s);
    
    }
    
    }
    
    }
    
    }
    
    
    @SpringBootApplication
    
    public class Application {
    
    public static void main(String[] args) {
    
    SpringApplication application = new SpringApplication(Application.class);
    
    ConfigurableApplicationContext context = application.run(args);
    
    context.close();
    
    }
    
    }

    启动Application类,可以看到TestCommandLineRunner.run()方法被执行。

    至于String... args参数,是用户在启动Application的时候,通过Run Configurations中的Arguments添加的参数(参数格式为KV格式,类似于--foo=bar),如下图所示

2.ApplicationRunner接口的使用

 
  1. @Component
    
    public class TestApplicationRunner implements ApplicationRunner {
    
    
    @Override
    
    public void run(ApplicationArguments args) throws Exception {
    
    System.out.println("服务启动,TestApplicationRunner执行启动加载任务...");
    
    String[] sourceArgs = args.getSourceArgs();
    
    if(null != sourceArgs){
    
    for (String s : sourceArgs) {
    
    System.out.println(s);
    
    }
    
    }
    
    }
    
    }
    
    @SpringBootApplication
    
    public class Application {
    
    public static void main(String[] args) {
    
    SpringApplication application = new SpringApplication(Application.class);
    
    ConfigurableApplicationContext context = application.run(args);
    
    context.close();
    
    }
    
    }
    

    ApplicationRunner接口的使用方式与CommandLineRunner接口基本相似,不同的只是run方法的参数类型,CommandLineRunner是基本类型,而ApplicationRunner是ApplicationArguments对象类型,经过一次简单封装,用户可对参数进行更多操作,具体可见ApplicationArguments的方法

    总结:用户使用CommandLineRunner或者ApplicationRunner接口均可实现应用启动初始化某些功能的需求,如果希望对参数有更多的操作,则可以选择实现ApplicationRunner接口。

3.CommandLineRunner、ApplicationRunner执行流程源码分析

    用户只要实现这两个接口,其中的run方法就会在项目启动时候被自动调用,那么究竟是在什么时候调用的呢?下面可以随笔者看一下Application的启动流程

    1)SpringApplication.run(args)

 
  1. public ConfigurableApplicationContext run(String... args) {
    
    StopWatch stopWatch = new StopWatch();
    
    stopWatch.start();
    
    ConfigurableApplicationContext context = null;
    
    FailureAnalyzers analyzers = null;
    
    configureHeadlessProperty();
    
    SpringApplicationRunListeners listeners = getRunListeners(args);
    
    listeners.started();
    
    try {
    
    // 对用户在Arguments输入的参数进行封装
    
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(
    
    args);
    
    ConfigurableEnvironment environment = prepareEnvironment(listeners,
    
    applicationArguments);
    
    Banner printedBanner = printBanner(environment);
    
    context = createApplicationContext();
    
    analyzers = new FailureAnalyzers(context);
    
    prepareContext(context, environment, listeners, applicationArguments,
    
    printedBanner);
    
    refreshContext(context);
    
    
    // 在更新完ApplicationContext之后,进行操作,run方法的调用就在这里被执行
    
    afterRefresh(context, applicationArguments);
    
    listeners.finished(context, null);
    
    stopWatch.stop();
    
    if (this.logStartupInfo) {
    
    new StartupInfoLogger(this.mainApplicationClass)
    
    .logStarted(getApplicationLog(), stopWatch);
    
    }
    
    return context;
    
    }
    
    catch (Throwable ex) {
    
    handleRunFailure(context, listeners, analyzers, ex);
    
    throw new IllegalStateException(ex);
    
    }
    
    }

    2)跟踪afterRefresh(ConfigurableApplicationContext context,ApplicationArguments args)方法,代码如下:

 
  1. protected void afterRefresh(ConfigurableApplicationContext context,
    
    ApplicationArguments args) {
    
    callRunners(context, args);
    
    }
    
    
    private void callRunners(ApplicationContext context, ApplicationArguments args) {
    
    List<Object> runners = new ArrayList<Object>();
    
    // 1.获取所有实现ApplicationRunner接口的类
    
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    
    // 2.获取所有实现CommandLineRunner的类
    
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    
    // 3.根据其@Order进行排序
    
    AnnotationAwareOrderComparator.sort(runners);
    
    for (Object runner : new LinkedHashSet<Object>(runners)) {
    
    // 4.调用ApplicationRunner其run方法
    
    if (runner instanceof ApplicationRunner) {
    
    callRunner((ApplicationRunner) runner, args);
    
    }
    
    // 5.调用CommandLineRunner其run方法
    
    if (runner instanceof CommandLineRunner) {
    
    callRunner((CommandLineRunner) runner, args);
    
    }
    
    }
    
    }

    3)跟踪context.getBeansOfType()方法,具体实现如下(在类DefaultListableBeanFactory中):

 
  1. protected void afterRefresh(ConfigurableApplicationContext context,
    
    ApplicationArguments args) {
    
    callRunners(context, args);
    
    }
    
    
    private void callRunners(ApplicationContext context, ApplicationArguments args) {
    
    List<Object> runners = new ArrayList<Object>();
    
    // 1.获取所有实现ApplicationRunner接口的类
    
    runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    
    // 2.获取所有实现CommandLineRunner的类
    
    runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    
    // 3.根据其@Order进行排序
    
    AnnotationAwareOrderComparator.sort(runners);
    
    for (Object runner : new LinkedHashSet<Object>(runners)) {
    
    // 4.调用ApplicationRunner其run方法
    
    if (runner instanceof ApplicationRunner) {
    
    callRunner((ApplicationRunner) runner, args);
    
    }
    
    // 5.调用CommandLineRunner其run方法
    
    if (runner instanceof CommandLineRunner) {
    
    callRunner((CommandLineRunner) runner, args);
    
    }
    
    }
    
    }

    总结:通过以上分析可知,实现这两个接口的类,在ApplicationContext.run()方法里被执行

猜你喜欢

转载自blog.csdn.net/u014252478/article/details/82460158