当前位置: 首页 > news >正文

Springboot扩展点之CommandLineRunner和ApplicationRunner

Springboot扩展点系列:

Springboot扩展点之ApplicationContextInitializer

Springboot扩展点之BeanFactoryPostProcessor

Springboot扩展点之BeanDefinitionRegistryPostProcessor

Springboot扩展点之BeanPostProcessor

Springboot扩展点之InstantiationAwareBeanPostProcessor

Springboot扩展点之SmartInstantiationAwareBeanPostProcessor

Springboot扩展点之ApplicationContextAwareProcessor

Springboot扩展点之@PostConstruct

Springboot扩展点之InitializingBean

Springboot扩展点之SmartInitializingSingleton

Springboot扩展点之CommandLineRunner和ApplicationRunner

Springboot扩展点之FactoryBean

Springboot扩展点之DisposableBean

Springboot扩展点系列之终结篇:Bean的生命周期


前言

大家都知道Springboot简化了Spring的开发,因此从某种意义来说,Spring的扩展点也是Springboot的扩展点,而这篇文章主角是CommandLineRunner和ApplicationRunner,而这两个是Springboot中新增的扩展点,之所以将这两个扩展点放在一起,是因为它两个功能特性高度相似,不同的只是名字、扩展方法形参数类型、执行先后的一些小的不同,那么下面就直接进入正题吧。

功能特性

1、CommandLineRunner和ApplicationRunner都有一个扩展方法run(),但是run()形参数类型不同;

2、CommandLineRunner.run()方法的形参数类型是String... args,ApplicationRunner.run()的形参数类型是ApplicationArguments args;

3、CommandLineRunner.run()的执行时机要晚于ApplicationRunner.run()一点;

4、CommandLineRunner和ApplicationRunner触发执行时机是在Spring容器、Tomcat容器正式启动完成后,可以正式处理业务请求前,即项目启动的最后一步;

5、CommandLineRunner和ApplicationRunner可以应用的场景:项目启动前,热点数据的预加载、清除临时文件、读取自定义配置信息等;

实现方式

1、定义MyCommandLineRunner和MyCommandLineRunner2,实现 CommandLineRunner, ApplicationRunner接口,并用@Order标记执行顺序,数字越小,执行知先级越高;

@Component
@Slf4j
@Order(value = 1)
public class MyCommandLineRunner implements CommandLineRunner, ApplicationRunner {@Overridepublic void run(String... args) throws Exception {log.info("----CommandLineRunner.run()触发执行,实现类是:"+this.getClass().getName());}@Overridepublic void run(ApplicationArguments args) throws Exception {log.info("----ApplicationRunner.run()触发执行,实现类是:"+this.getClass().getName());}
}
@Component
@Slf4j
@Order(value = 2)
public class MyCommandLineRunner2 implements CommandLineRunner, ApplicationRunner {@Overridepublic void run(String... args) throws Exception {log.info("----CommandLineRunner.run()触发执行,实现类是:"+this.getClass().getName());}@Overridepublic void run(ApplicationArguments args) throws Exception {log.info("----ApplicationRunner.run()触发执行,实现类是:"+this.getClass().getName());}
}

2、使用SpringApplication.run()启动SprIngboot项目;

@SpringBootApplication
@Slf4j
public class FanfuApplication {public static void main(String[] args) {log.info("----Springboot开始启动....");SpringApplication.run(FanfuApplication.class, args);log.info("----Springboot启动完成");}
}

3、启动完成,日志结果如图:

工作原理

之前基他的扩展点的执行时机大部分是在Spring容器实例化前后、Spring容器刷新中,CommandLineRunner和ApplicationRunner与之前其他的扩展点不同的是,其执行时机最晚,即在Spring容器、Tomcat容器正式启动完成的最后一步。

1、顺着SpringApplication.run(FanfuApplication.class, args)进入到run(String... args)中,CommandLineRunner和ApplicationRunner的执行入口就在这里,之前在其他分享其他扩展点时,经常遇到的AbstractApplicationContext#refresh(),其实是第25行 refreshContext(context)中触发的。

public ConfigurableApplicationContext run(String... args) {//springboot启动前的准备工作StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();configureHeadlessProperty();//启动要开始的时候触发了SpringApplicationRunListenersSpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting();try {//启动参数args包装ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);//准备系统环境ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);configureIgnoreBeanInfo(environment);//打印启动时标志图形Banner printedBanner = printBanner(environment);//创建Spring的上下文环境context = createApplicationContext();exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);prepareContext(context, environment, listeners, applicationArguments, printedBanner);//刷新Spring容器refreshContext(context);//容器启动后的一些后置处理afterRefresh(context, applicationArguments);stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}//触发了Spring容器启动完成的事件listeners.started(context);//开始调用CommandLineRunner和ApplicationRunner的扩展点方法callRunners(context, applicationArguments);}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, listeners);throw new IllegalStateException(ex);}try {listeners.running(context);}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, null);throw new IllegalStateException(ex);}return context;
}

2、CommandLineRunner和ApplicationRunner的扩展点方法的调用逻辑,其实也是简单易懂,先把所有CommandLineRunner和ApplicationRunner的实现类汇总到一个集合,然后循环遍历这个集合,在集合里判断,如果ApplicationRunner的实现类,则先执行;如果是CommandLineRunner的实现类,则后执行;非常的朴实无华。

private void callRunners(ApplicationContext context, ApplicationArguments args) {//汇总CommandLineRunner和ApplicationRunner的实现类到runners集合List<Object> runners = new ArrayList<>();runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());AnnotationAwareOrderComparator.sort(runners);//循环遍历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);}}
}
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);}
}

总结

如果面试过程中,有面试官这样问你:”对业务上一些热点数据需要在项目启动前进行预加载,你有什么好的办法吗?“

你可以这么回答他:”我了解Springboot有两个扩展点:CommandLineRunner和ApplicationRunner,其触发执行时机是在Spring容器、Tomcat容器正式启动完成后,可以正式处理业务请求前,刚好可以做一些热点数据预先加载完全可以使用这个方法,实现方式也很简单,实现CommandLineRunner或ApplicationRunner接口即可,非常优雅。“

如果你这么回答他,相信他会满意的。

http://www.lryc.cn/news/11920.html

相关文章:

  • ngixn 常用配置之文件类型与自定义 log
  • 【100个 Unity实用技能】 | Unity 通过自定义菜单将资源导出
  • 0.3调试opencv源码的两种方式
  • Redis的常见操作和Session的持久化
  • TypeScript笔记(二)
  • 【MyBatis】源码学习 03 - 类型处理器 TypeHandler
  • 建造《流浪地球2》中要毁灭人类的超级量子计算机MOSS的核心量子技术是什么?
  • 数据结构~七大排序算法(Java实现)
  • python练习
  • RPC-thrift实践
  • Maven:工程的拆分与聚合
  • 使用uniapp创建小程序和H5界面
  • 密度峰值聚类算法(DPC)
  • RabbitMQ相关问题
  • 操作系统 三(存储管理)
  • day34 贪心算法 | 860、柠檬水找零 406、根据身高重建队列 452、用最少数量的箭引爆气球
  • 使用canvas给上传的整张图片添加平铺的水印
  • [安装之4] 联想ThinkPad 加装固态硬盘教程
  • Java数据类型、基本与引用数据类型区别、装箱与拆箱、a=a+b与a+=b区别
  • GoLang设置gofmt和goimports自动格式化
  • 【k8s】如何搭建搭建k8s服务器集群(Kubernetes)
  • DIDL4_前向传播与反向传播(模型参数的更新)
  • 链表学习之链表划分
  • (考研湖科大教书匠计算机网络)第五章传输层-第一、二节:传输层概述及端口号、复用分用等概念
  • C#:Krypton控件使用方法详解(第七讲) ——kryptonHeader
  • 5年软件测试工程师分享的自动化测试经验,一定要看
  • 什么是猜疑心理?小猫测试网科普小作文
  • Redis命令行对常用数据结构String、list、set、zset、hash等增删改查操作
  • mycobot 使用教程
  • JVM学习总结,虚拟机性能监控、故障处理工具:jps、jstat、jinfo、jmap、Visual VM、jstack等