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

【Java开发日记】说一说 SpringBoot 中 CommandLineRunner

目录

1、CommandLineRunner

SpringBoot中CommandLineRunner的作用

简单例子

多个类实现CommandLineRunner接口执行顺序的保证

通过实现Ordered接口实现控制执行顺序

通过@Order注解实现控制执行顺序

@Order 作用

2、ApplicationRunner

3、传递参数

4、源码跟踪

run()方法

callRunners方法


1、CommandLineRunner

SpringBoot中CommandLineRunner的作用

平常开发中有可能需要实现在项目启动后执行的功能,SpringBoot提供的一种简单的实现方案就是添加一个model并实现CommandLineRunner接口,实现功能的代码放在实现的run方法中。也就是项目一启动之后,就立即需要执行的动作。只需要在项目里面简单的配置,就可以实现这个功能。 

简单例子
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class MyStartupRunner implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println("项目已经启动");}
}

多个类实现CommandLineRunner接口执行顺序的保证

通过实现Ordered接口实现控制执行顺序
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
/*** 优先级最高* 该类期望在springboot 启动后第一顺位执行* @since 12:57**/
@Slf4j
@Component
public class HighOrderCommandLineRunner implements CommandLineRunner, Ordered {@Overridepublic void run(String... args) throws Exception {for (String arg : args) {log.info("arg = " + arg);}log.info("i am highOrderRunner");}@Overridepublic int getOrder() {return Integer.MIN_VALUE+1;}
}
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
/*** 优先级低于{@code HighOrderCommandLineRunner}* @since 12:59**/
@Slf4j
@Component
public class LowOrderCommandLineRunner implements CommandLineRunner, Ordered {@Overridepublic void run(String... args) throws Exception {log.info("i am lowOrderRunner");}@Overridepublic int getOrder() {return Integer.MIN_VALUE+1;}
}

启动Spring Boot程序后,控制台按照预定的顺序打印出了结果:

2020-05-30 23:11:03.685  INFO 11976 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-05-30 23:11:03.701  INFO 11976 --- [           main] c.f.Application  : Started SpringBootApplication in 4.272 seconds (JVM running for 6.316)
2020-05-30 23:11:03.706  INFO 11976 --- [           main] c.f.HighOrderCommandLineRunner   : i am highOrderRunner
2020-05-30 23:11:03.706  INFO 11976 --- [           main] c.f.LowOrderCommandLineRunner   : i am lowOrderRunner

通过@Order注解实现控制执行顺序

SpringBoot在项目启动后会遍历所有实现CommandLineRunner的实体类并执行run方法,如果需要按照一定的顺序去执行,那么就需要在实体类上使用一个@Order注解(或者实现Order接口)来表明顺序

import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(value=2)
public class MyStartupRunner1 implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println("执行2");}
}
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(value=1)
public class MyStartupRunner2 implements CommandLineRunner {@Overridepublic void run(String... args) throws Exception {System.out.println("执行1");}
}

控制台显示

执行1
执行2

根据控制台结果可判断,@Order 注解的执行优先级是按value值从小到大顺序。

@Order 作用

项目启动之后,要执行的动作是比较的多,那么到底先执行哪个,那么就可以利用这个注解限定优先级。 :::danger Ordered接口并不能被 @Order注解所代替。

2、ApplicationRunner

在Spring Boot 1.3.0又引入了一个和CommandLineRunner功能一样的接口ApplicationRunnerCommandLineRunner接收可变参数String... args,而ApplicationRunner 接收一个封装好的对象参数ApplicationArguments。除此之外它们功能完全一样,甚至连方法名都一样。声明一个ApplicationRunner并让它优先级最低:

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
/*** 优先级最低**/
@Slf4j
@Component
public class DefaultApplicationRunner implements ApplicationRunner, Ordered {@Overridepublic void run(ApplicationArguments args) throws Exception {log.info("i am applicationRunner");Set<String> optionNames = args.getOptionNames();log.info("optionNames = " + optionNames);String[] sourceArgs = args.getSourceArgs();log.info("sourceArgs = " + Arrays.toString(sourceArgs));List<String> nonOptionArgs = args.getNonOptionArgs();log.info("nonOptionArgs = " + nonOptionArgs);List<String> optionValues = args.getOptionValues("foo");log.info("optionValues = " + optionValues);}@Overridepublic int getOrder() {return Integer.MIN_VALUE+2;}
}

按照顺序打印了三个类的执行结果:

2020-06-01 13:02:39.420  INFO 19032 --- [           main] c.f.MybatisResultmapApplication  : Started MybatisResultmapApplication in 1.801 seconds (JVM running for 2.266)
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.HighOrderCommandLineRunner   : i am highOrderRunner
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.LowOrderCommandLineRunner    : i am lowOrderRunner
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : i am applicationRunner
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : optionNames = []
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : sourceArgs = []
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : nonOptionArgs = []
2020-06-01 13:02:39.423  INFO 19032 --- [           main] c.f.DefaultApplicationRunner     : optionValues = null
 optionValues = null

Ordered接口并不能被 @Order注解所代替。

3、传递参数

Spring Boot应用启动时是可以接受参数的,换句话说也就是Spring Bootmain方法是可以接受参数的。这些参数通过命令行 java -jar yourapp.jar 来传递。CommandLineRunner会原封不动照单全收这些接口,这些参数也可以封装到ApplicationArguments对象中供ApplicationRunner调用。看一下ApplicationArguments的相关方法:

  • getSourceArgs() 被传递给应用程序的原始参数,返回这些参数的字符串数组。
  • getOptionNames() 获取选项名称的Set字符串集合。如 --spring.profiles.active=dev --debug 将返回["spring.profiles.active","debug"] 。
  • getOptionValues(String name) 通过名称来获取该名称对应的选项值。如--foo=bar --foo=baz 将返回["bar","baz"]
  • containsOption(String name) 用来判断是否包含某个选项的名称。
  • getNonOptionArgs() 用来获取所有的无选项参数。

可以通过下面的命令运行一个 Spring Boot应用 Jar

java -jar yourapp.jar --foo=bar --foo=baz --dev.name=fcant java fcantcn

或者在IDEA开发工具中打开Spring Boot应用main方法的配置项,进行命令行参数的配置,其他IDE工具同理。
运行Spring Boot应用,将会打印出:

2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = --foo=bar
2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = --foo=baz
2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = --dev.name=fcant
2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = java
2020-06-01 15:04:31.490  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : arg = fcantcn
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.HighOrderCommandLineRunner   : i am highOrderRunner
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.LowOrderCommandLineRunner    : i am lowOrderRunner
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : i am applicationRunner
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : optionNames = [dev.name, foo]
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : sourceArgs = [--foo=bar, --foo=baz, --dev.name=fcant, java, fcantcn]
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : nonOptionArgs = [java, fcantcn]
2020-06-01 15:04:31.491  INFO 13208 --- [           main] c.f.DefaultApplicationRunner     : optionValues = [bar, baz]

然后就可以根据实际需要动态地执行一些逻辑。 

4、源码跟踪

通过源码理解一下底层实现。 

run()方法

跟进run方法后,一路F6直达以下方法

public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();//设置线程启动计时器stopWatch.start();ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();//配置系统属性:默认缺失外部显示屏等允许启动configureHeadlessProperty();//获取并启动事件监听器,如果项目中没有其他监听器,则默认只有EventPublishingRunListenerSpringApplicationRunListeners listeners = getRunListeners(args);//将事件广播给listenerslisteners.starting();try {//对于实现ApplicationRunner接口,用户设置ApplicationArguments参数进行封装ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);//配置运行环境:例如激活应用***.yml配置文件      ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);configureIgnoreBeanInfo(environment);//加载配置的banner(gif,txt...),即控制台图样Banner printedBanner = printBanner(environment);//创建上下文对象,并实例化context = createApplicationContext();exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);//配置SPring容器      prepareContext(context, environment, listeners, applicationArguments,printedBanner);//刷新Spring上下文,创建bean过程中      refreshContext(context);//空方法,子类实现afterRefresh(context, applicationArguments);//停止计时器:计算线程启动共用时间stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}//停止事件监听器listeners.started(context);//开始加载资源callRunners(context, applicationArguments);}catch (Throwable ex) {handleRunFailure(context, listeners, exceptionReporters, ex);throw new IllegalStateException(ex);}listeners.running(context);return context;
}

主要是熟悉SpringBoot的CommandLineRunner接口实现原理。因此上面SpringBoot启动过程方法不做过多介绍。直接进入CallRunners()方法内部。 

callRunners方法
private void callRunners(ApplicationContext context, ApplicationArguments args) {//将实现ApplicationRunner和CommandLineRunner接口的类,存储到集合中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);}}
}
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {try {//调用各个实现类中的逻辑实现(runner).run(args.getSourceArgs());}catch (Exception ex) {throw new IllegalStateException("Failed to execute CommandLineRunner", ex);}
}

到此结束,再跟进run()方法,就可以看到资源加载逻辑。

如果小假的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!

http://www.xdnf.cn/news/12593.html

相关文章:

  • PyTorch 中contiguous函数使用详解和代码演示
  • 第4章——springboot自动配置
  • LabVIEW音频测试分析
  • ​React Hooks 的闭包陷阱问题
  • YoloV12改进策略:Block改进|TAB,融合组内自注意力(IASA)和组间交叉注意力(IRCA)|即插即用
  • spring:实例工厂方法获取bean
  • 408考研逐题详解:2009年第33题
  • 第22讲、Odoo18 QWeb 模板引擎详解
  • 【PCIe总线】 -- PCI、PCIe相关实现
  • 第十五届蓝桥杯单片机国赛
  • SQL-labs通关(level1-22)
  • 主流信创数据库对向量功能的支持对比
  • Vue Fragment vs React Fragment
  • Mysql-定时删除数据库中的验证码
  • Golang基础学习
  • 性能测试-jmeter实战2
  • 注意力热图可视化
  • Ubuntu 下开机自动执行命令的方法
  • win11无法打开.bat文件、打开.bat文件闪退解决方案,星露谷smapi mod安装时,.bat安装文件一闪而
  • CMake基础:构建流程详解
  • 变幻莫测:CoreData 中 Transformable 类型面面俱到(一)
  • 通过日志分析华为CangjieMagic智能体框架的工作机理
  • CAD2025安装教程与资源下载
  • AI 开发之 Python 编程技能
  • Redis 实现分布式锁:深入剖析与最佳实践(含Java实现)
  • Mybatis 拦截器 与 PageHelper 源码解析
  • ⚡️ Linux Docker 基本命令参数详解
  • Kafka 消息模式实战:从简单队列到流处理(二)
  • 深入浅出Docker
  • 【Go语言基础【14】】defer与异常处理(panic、recover)