Bootstrap

SpringBoot日常:扩展接口之CommandLineRunner和ApplicationRunner

简介

日常开发中我们可能经常会遇到这样的场景,像启动容器完成后需要去执行一些业务逻辑,SpringBoot给我们提供了两个接口来帮助我们实现这种需求,两个启动加载接口分别是CommandLineRunner和ApplicationRunner。本文简单介绍如何使用该扩展点实现业务

springboot启动容器过程

首先我们来看看springboot启动容器过程源码,从下列源码启动过程可以看到,在所有事项准备完成后,最终会执行所有 Runner 运行器,其中就包括ApplicationRunner和CommandLineRunner

/**
 * 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) {
	// 获取系统时间,开始计时器
	long startTime = System.nanoTime();
	DefaultBootstrapContext bootstrapContext = createBootstrapContext();
	ConfigurableApplicationContext context = null;
	configureHeadlessProperty();
	// 创建所有 Spring 运行监听器并发布应用启动事件
	SpringApplicationRunListeners listeners = getRunListeners(args);
	listeners.starting(bootstrapContext, this.mainApplicationClass);
	try {
		// 处理 args 参数
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		
		// 准备环境
		ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
		configureIgnoreBeanInfo(environment);
		Banner printedBanner = printBanner(environment);

		// 创建应用上下文
		context = createApplicationContext();
		context.setApplicationStartup(this.applicationStartup);
		
		// 准备应用上下文
		prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
		// 刷新应用上下文
		refreshContext(context);
		// 应用上下文刷新之后的事件的处理
		afterRefresh(context, applicationArguments);
		Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
		if (this.logStartupInfo) {
			new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
		}
		// 发布应用上下文启动完成事件
		listeners.started(context, timeTakenToStartup);
		// 执行所有 Runner 运行器
		callRunners(context, applicationArguments);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, listeners);
		throw new IllegalStateException(ex);
	}
	try {
		Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
		// 发布应用上下文就绪事件
		listeners.ready(context, timeTakenToReady);
	}
	catch (Throwable ex) {
		handleRunFailure(context, ex, null);
		throw new IllegalStateException(ex);
	}
	return context;
}
private void callRunners(ApplicationContext context, ApplicationArguments args) {
	//将实现ApplicationRunner和CommandLineRunner接口的类,存储到集合中
	List<Object> runners = new ArrayList<>();
	// 从Spring容器中查找类型为ApplicationRunner的Bean
	runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
	// 从Spring容器中查找类型为CommandLineRunner的Bean
	runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());

	 //按照加载先后顺序排序
	AnnotationAwareOrderComparator.sort(runners);

	//调用执行
	for (Object runner : new LinkedHashSet<>(runners)) {
		//如果是ApplicationRunner的实例
		if (runner instanceof ApplicationRunner) {
			callRunner((ApplicationRunner) runner, args);
		}
		//如果是CommandLineRunner的实例
		if (runner instanceof CommandLineRunner) {
			callRunner((CommandLineRunner) runner, args);
		}
	}
}

应用场景

ApplicationRunner和CommandLineRunner实在容器启动完成后才执行,所以常常会用在一些初始化的场景,比如

  • 缓存数据的预加载
  • 定时任务的执行
  • 升级脚本的数据刷新

如何控制执行顺序

如果存在多个实现类,可以通过注解@Order注解或实现Ordered接口来控制执行顺序

@Order(2)
public class aaaCommandLineRunner implements CommandLineRunner {

ApplicationRunner和CommandLineRunner区别

ApplicationRunner和CommandLineRunner的作用是相同的。不同之处在于ApplicationRunner接口的run()方法接收ApplicationArguments对象作为参数,是对原始参数做了进一步的封装,而CommandLineRunner接口的run()方法接收String数组作为参数,即是最原始的参数,没有做任何处理。

代码示例

@Slf4j
@Configuration
public class ExtendApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("ApplicationRunner--Extend--run {}",args);
    }
}
@Slf4j
@Configuration
@Order(2)
public class ExtendCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        log.info("CommandLineRunner--Extend--run{}",args);
    }
}

运行示例

在这里插入图片描述

;