1. 简介
如果你需要在SpringApplication启动后运行一些特定的代码,可以实现ApplicationRunner或CommandLineRunner接口。这两个接口的工作方式相同,都提供了一个run方法,在SpringApplication.run(…)完成之前调用。
2. 二者区别
ApplicationRunner方法签名
void run(ApplicationArguments args) throws Exception ;
CommandLineRunner方法签名
void run(String... args) throws Exception ;
这两个类的方法名都是run,仅仅是他们的参数不同,接下来查看具体参数有何不同
首先,在启动程序时添加如下参数
--pack.title=xxxooo --pack.version=1.0.1
ApplicationRunner参数
public void run(ApplicationArguments args) throws Exception {
System.out.printf("AR Args: %s%n", Arrays.toString(args.getSourceArgs())) ;
System.out.printf("AR 参数名: %s%n", args.getOptionNames()) ;
}
输出结果
通过上面的输出对比,ApplicationRunner对参数的操作方法更加的完善不仅包括了完整原始的参数信息,还将提供了其它获取所有参数名及参数名对应的值(上面并未调用对应方法getOptionValues)信息。
3. 执行时机
当执行SpringApplication#run方法时,最终执行如下的核心方法
public class SpringApplication {
public ConfigurableApplicationContext run(String... args) {
// ...
try {
// 准备上下文
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 初始化Spring容器
refreshContext(context);
afterRefresh(context, applicationArguments);
// ...
// 调用ApplicationRunner和CommandLineRunner
callRunners(context, applicationArguments);
}
try {
if (context.isRunning()) {
listeners.ready(context, startup.ready());
}
}
return context;
}
}
这2个Runner执行顺序如下
private void callRunner(Runner runner, ApplicationArguments args) {
if (runner instanceof ApplicationRunner) {
callRunner(ApplicationRunner.class, runner, (applicationRunner) -> applicationRunner.run(args));
}
if (runner instanceof CommandLineRunner) {
callRunner(CommandLineRunner.class, runner,
(commandLineRunner) -> commandLineRunner.run(args.getSourceArgs()));
}
}
先执行ApplicationRunner。
4. 实战案例
案例1
应用启动后,执行定时任务
@Component
public class TaskSchedulerInitializer implements ApplicationRunner {
private final TaskScheduler taskScheduler;
public TaskSchedulerInitializer(TaskScheduler taskScheduler) {
this.taskScheduler = taskScheduler;
}
@Override
public void run(ApplicationArguments args) throws Exception {
taskScheduler.schedule(this::task, new CronTrigger("0/5 * * * * ?")) ;
}
private void task() {
System.out.println("执行任务...") ;
}
}
注:默认你还需要配置TaskScheduler。
案例2
预热缓存,容器启动后加载缓存数据到内存
@Component
public class CacheDataWarmup implements ApplicationRunner {
private final CacheManager cacheManager ;
private final SysDictService dictService ;
public CacheDataWarmup(CacheManager cacheManager, SysDictService dictService) {
this.cacheManager = cacheManager ;
this.dictService = dictService ;
}
@Override
public void run(ApplicationArguments args) throws Exception {
List<SysDict> dicts = this.dictService.queryAll() ;
this.cacheManager.getCache("dicts").put("allparams", dicts) ;
}
}
注:确保你的环境中引入了cache starter(你也可以不引入,自己实现)。
案例3
加载外部资源文件
@Component
public class LoadOuterResource implements ApplicationRunner {
private final ConfigurableEnvironment environment ;
public LoadOuterResource(ConfigurableEnvironment environment) {
this.environment = environment ;
}
@Override
public void run(ApplicationArguments args) throws Exception {
YamlPropertySourceLoader sourceLoader = new YamlPropertySourceLoader() ;
List<PropertySource<?>> list;
try {
list = sourceLoader.load("pack", new ClassPathResource("com/pack/binder/properties/pack.yml"));
list.forEach(propertySource -> environment.getPropertySources().addLast(propertySource)) ;
} catch (IOException e) {
e.printStackTrace() ;
}
}
}
以上案例,都有其它的处理方式,这里只是给大家演示这里的Runner都能做些什么。
5. 控制顺序
如果你项目中有多个Runner实现,并且希望按照一定的顺序执行,那么你可以通过如下方式定义执行顺序
-
实现org.springframework.core.Ordered接口
-
使用@Order注解
如下示例:
@Component
@Order(2)
public class FirstRunner implements ApplicationRunner {
// ...
}
@Component
@Order(1)
public class SecondRunner implements ApplicationRunner {
// ...
}
上面执行分别:SecondRunner -> FirstRunner。