SpringBoot启动器加载器-run方法(callRunners)

本文详细介绍了SpringBoot的启动过程,特别是`SpringApplication.run`方法的内部逻辑。它首先获取`ApplicationRunner`和`CommandLineRunner`的Bean并按`Order`值排序,然后执行它们的`run`方法。当`Order`相同,`ApplicationRunner`会先于`CommandLineRunner`执行。`ApplicationArguments`用于接收启动参数,其值优于项目内配置。不同之处在于,`ApplicationRunner`的参数已经解析,而`CommandLineRunner`的参数未经处理。

在项目开发中,经常需要在项目启动的时候去读取配置文件、或者把数据库的数据加载到缓存中。Spring Boot提供了ApplicationRunner和CommandLineRunner来帮助我们实现这些需求,他们都是在Spring容器初始化完毕之后执行起run方法。

SpringApplication-run方法

callRunners(context, applicationArguments);

public ConfigurableApplicationContext run(String... args) {
    // 1、创建并启动计时监控类
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    // 2、初始化应用上下文和异常报告集合
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    // 3、设置系统属性 `java.awt.headless` 的值,默认值为:true
    configureHeadlessProperty();
    // 4、创建所有 Spring 运行监听器并发布应用启动事件
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
 
    try {
        // 5、 参数封装,也就是在命令行下启动应用带的参数,如--server.port=9000
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        // 6、根据运行监听器和应用参数来准备 Spring 环境(本文重点)
        ConfigurableEnvironment environment = prepareEnvironment(listeners,
                applicationArguments);
        configureIgnoreBeanInfo(environment);
        // 7、创建 Banner 打印类
        Banner printedBanner = printBanner(environment);
        // 8、创建应用上下文
        context = createApplicationContext();
        // 9、准备异常报告器
        exceptionReporters = getSpringFactoriesInstances(
                SpringBootExceptionReporter.class,
                new Class[] { ConfigurableApplicationContext.class }, context);
        // 10、准备应用上下文
        prepareContext(context, environment, listeners, applicationArguments,
                printedBanner);
        // 11、刷新应用上下文
        refreshContext(context);
        // 12、应用上下文刷新后置处理
        afterRefresh(context, applicationArguments);
        // 13、停止计时监控类
        stopWatch.stop();
        // 14、输出日志记录执行主类名、时间信息
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        // 15、发布应用上下文启动完成事件
        listeners.started(context);
        // 16、执行所有 Runner 运行器
        callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, listeners);
        throw new IllegalStateException(ex);
    }
    try {
        // 17、发布应用上下文就绪事件
        listeners.running(context);
    }
    catch (Throwable ex) {
        handleRunFailure(context, ex, exceptionReporters, null);
        throw new IllegalStateException(ex);
    }
    // 18、返回应用上下文
    return context;
}

实现逻辑

让我们来看看这个方法callRunners(context, applicationArguments);

逻辑比较简单分为三部分

  • 首先分别获取ApplicationRunner和CommandLineRunner的bean放入List中
  • 根据实现类上面的Order值进行排序
  • 分别执行它们的run方法
	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);
		for (Object runner : new LinkedHashSet<>(runners)) {
			if (runner instanceof ApplicationRunner) {
				callRunner((ApplicationRunner) runner, args);
			}
			if (runner instanceof CommandLineRunner) {
				callRunner((CommandLineRunner) runner, args);
			}
		}
	}

那么如果是ApplicationRunner和CommandLineRunner的order是相同的谁会先执行呢?

  • 准备两个ApplicationRunner实现类的Order值为1,2
  • 准备两个CommandLineRunner实现类的Order值为1,2

测试结果

image.png

Order值相同的话ApplicationRunner先执行

在callRunner中发现ApplicationRunner和CommandLineRunner的run方法args用的不一样

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

ApplicationArguments args是什么

  • 传递参数的一种方式; 例如启动的时候 java -jar --spring.profiles.active=prod
  • 使用方式是 --key=value
    它的配置优先于项目里面的配置;

在run方法里面我们跟踪这个看

ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);

找到解析args的类

class SimpleCommandLineArgsParser {

	/**
	 * Parse the given {@code String} array based on the rules described {@linkplain
	 * SimpleCommandLineArgsParser above}, returning a fully-populated
	 * {@link CommandLineArgs} object.
	 * @param args command line arguments, typically from a {@code main()} method
	 */
	public CommandLineArgs parse(String... args) {
		CommandLineArgs commandLineArgs = new CommandLineArgs();
		for (String arg : args) {
			if (arg.startsWith("--")) {
				String optionText = arg.substring(2, arg.length());
				String optionName;
				String optionValue = null;
				if (optionText.contains("=")) {
					optionName = optionText.substring(0, optionText.indexOf('='));
					optionValue = optionText.substring(optionText.indexOf('=')+1, optionText.length());
				}
				else {
					optionName = optionText;
				}
				if (optionName.isEmpty() || (optionValue != null && optionValue.isEmpty())) {
					throw new IllegalArgumentException("Invalid argument syntax: " + arg);
				}
				commandLineArgs.addOptionArg(optionName, optionValue);
			}
			else {
				commandLineArgs.addNonOptionArg(arg);
			}
		}
		return commandLineArgs;
	}

}

就是解析--key=value的格式。

我们在启动的时候加上参数

image.png

通过断点可以清楚的看到,上面是ApplicationRunner,下面是CommandLineRunner的入参
image.png

区别

  • ApplicationRunner优先于CommandLineRunner执行,在Order相同的情况下
  • ApplicationRunner的入参是解析过的,CommandLineRunner的入参没有处理
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

原飞木

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值