spring boot 2源码系列(四)- ApplicationRunner和CommandLineRunner

spring boot工程启动成功后会回调ApplicationRunner和CommandLineRunner接口的run方法,这两个接口主要有两点不同:

1、ApplicationRunner的run(ApplicationArguments args)接收一个ApplicationArguments类型的参数,ApplicationArguments是对原始程序参数的封装类。CommandLineRunner的run(String... args)接收原始的程序参数。

2、若未加上@Order注解定义排序值或排序值相同,ApplicationRunner比CommandLineRunner先运行。

ApplicationRunner、CommandLineRunner使用

1、新建4个类

//未定义@Order排序值
@Component
public class ApplicationRunnerOne implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("#######启动加载器--ApplicationRunnerOne。原始程序参数:"+args.getSourceArgs()
                +"。test="+args.getOptionValues("test").toString());
    }
}
//@Order排序值为2
@Order(2)
@Component
public class ApplicationRunnerTwo implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("#######启动加载器--ApplicationRunnerTwo");
    }
}
//未定义@Order排序值
@Component
public class CommandLineRunner1 implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("#######启动加载器--CommandLineRunner1");
    }
}
//@Order排序值为2
@Order(2)
@Component
public class CommandLineRunner2 implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("#######启动加载器--CommandLineRunner2");
    }
}

2、添加程序参数 --test=value01

spring boot 2源码系列(四)- ApplicationRunner和CommandLineRunner_第1张图片

3、启动spring boot,控制台输出如下:

#######启动加载器--ApplicationRunnerTwo
#######启动加载器--CommandLineRunner2
#######启动加载器--ApplicationRunnerOne。原始程序参数:[Ljava.lang.String;@39fa8ad2。test=[value01]
#######启动加载器--CommandLineRunner1

ApplicationRunnerTwo、CommandLineRunner2的@Order排序值相同,ApplicationRunnerTwo比CommandLineRunner2先运行。没加上@Order排序值的后运行。

源码

ApplicationRunner、CommandLineRunner的源码比较简单,只需要在实现类的run方法中打一个断点,程序调用栈就很清晰了。

spring boot 2源码系列(四)- ApplicationRunner和CommandLineRunner_第2张图片

调用栈不深,开始分析源码。

1、在 SpringApplication#run(java.lang.String...) 中,ApplicationStartedEvent事件发布、监听完成之后,便调用Runner。

// 源码位置 org.springframework.boot.SpringApplication#run(java.lang.String...)
// 仅列出主要代码
public ConfigurableApplicationContext run(String... args) {

    // 使用程序参数创建ApplicationArguments对象
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

    listeners.started(context);

    // 在ApplicationStartedEvent事件之后执行Runners
    callRunners(context, applicationArguments);

}

1.1 ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); 源码

// 源码位置 org.springframework.boot.DefaultApplicationArguments#DefaultApplicationArguments
public DefaultApplicationArguments(String... args) {
    Assert.notNull(args, "Args must not be null");
    // 通过args创建this.source
    this.source = new DefaultApplicationArguments.Source(args);
    // 依旧将args原始值存储在this.args属性中
    this.args = args;
}

1.1.2 对args的处理在 org.springframework.core.env.SimpleCommandLineArgsParser#parse(String... args) 方法中,主要逻辑是通过  -- 和 = 分隔程序参数。

1.2、callRunners(context, applicationArguments); 源码

// 源码位置 org.springframework.boot.SpringApplication#callRunners
private void callRunners(ApplicationContext context, ApplicationArguments args) {
    List runners = new ArrayList<>();
    /**
     * getBeansOfType(@Nullable Class type) 获取type类型的bean,返回值是一个map。
     * runners.addAll(XXX)将ApplicationRunner、CommandLineRunner的实现类合并到runners列表中
     *
     * 因为先添加ApplicationRunner,所以排序值相同的情况下,ApplicationRunner比CommandLineRunner先运行。
     */
    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) {
            // 调用ApplicationRunner
            callRunner((ApplicationRunner) runner, args);
        }
        if (runner instanceof CommandLineRunner) {
            // 调用CommandLineRunner
            callRunner((CommandLineRunner) runner, args);
        }
    }
} 
  

1.2.1 AnnotationAwareOrderComparator.sort(runners); 通过排序值对类进行排序,在 spring boot 2源码系列(一)- 系统初始化器ApplicationContextInitializer 博客中有讲过,本文不再重复。仅补充一点,@Order(2)注解的类比没添加@Order注解的类先运行,这是因为:没添加@Order注解,排序值是 Ordered.LOWEST_PRECEDENCE 最低优先级。Ordered.LOWEST_PRECEDENCE的数值是Integer.MAX_VALUE。数值越大,排序越靠后。

/**
 * 源码位置org.springframework.core.OrderComparator#getOrder(java.lang.Object)
 * 类上没有@Order注解或者没实现Ordered接口,返回 Ordered.LOWEST_PRECEDENCE 排序值
 */
protected int getOrder(@Nullable Object obj) {
    if (obj != null) {
        //
        Integer order = findOrder(obj);
        if (order != null) {
            return order;
        }
    }
    return Ordered.LOWEST_PRECEDENCE;
}

1.2.2 callRunner((ApplicationRunner) runner, args);  和 callRunner((CommandLineRunner) runner, args);  源码

// callRunner((ApplicationRunner) runner, args);
// 源码位置 org.springframework.boot.SpringApplication#callRunner(org.springframework.boot.ApplicationRunner, org.springframework.boot.ApplicationArguments)
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
    try {
        // 调用ApplicationRunner实现类的run方法
        (runner).run(args);
    }
    catch (Exception ex) {
        throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
    }
}
// callRunner((CommandLineRunner) runner, args);
// 源码位置 org.springframework.boot.SpringApplication.callRunner(org.springframework.boot.CommandLineRunner, org.springframework.boot.ApplicationArguments)
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
    try {
        /**
         * 通过 args.getSourceArgs() 获取原始程序参数
         * 调用CommandLineRunner实现类run方法
         */
        (runner).run(args.getSourceArgs());
    }
    catch (Exception ex) {
        throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
    }
}

 

 

你可能感兴趣的:(spring,boot)