【Spring系列】应用启动后回调机制CommandLineRunner和ApplicationRunner接口
发布日期:2021-06-30 12:37:38 浏览次数:4 分类:技术文章

本文共 7173 字,大约阅读时间需要 23 分钟。

目录


一、前言

如果我们想在应用启动时,搞事情,并且在整个应用生命周期只搞一次,有什么办法呢?比如在应用启动的时候,提前从数据库加载一些数据提前加载加密证书等。

其实 SpringBoot 有2大法宝可以做到,CommandLineRunner 和 ApplicationRunner 接口。这2个接口里面都只有一个 run 方法,只要实现了这2个接口的类,并且重写了 run 方法,并将它们的实例交由 Spring 容器管理,Spring 容器在启动后,会自动调用执行 run 方法。并且在整个应用生命周期内只会执行一次。

二、CommandLineRunner 接口说明

package org.springframework.boot;@FunctionalInterfacepublic interface CommandLineRunner {
void run(String... args) throws Exception;}

我们定义一个类实现 CommandLineRunner 接口,测试下它的调用时机。我们直接用启动类来实现接口,当然你也可以另起一个类来实现 CommandLineRunner,并注入到 Spring 容器中。

@SpringBootApplicationpublic class Application implements CommandLineRunner {
public static void main(String[] args) {
System.out.println("--- Application main begin to start..."); SpringApplication.run(Application.class, args); System.out.println("--- Application main has start..."); } @Override public void run(String... args) throws Exception {
System.out.println("--- CommandLineRunner in Application..."); }}

启动日志如下,证明确实会在应用启动时,即 Spring 容器加载完后,执行我们定义的 run 方法,执行完成后项目启动完成。

--- Application main begin to start...  .   ____          _            __ _ _ /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/  ___)| |_)| | | | | || (_| |  ) ) ) )  '  |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot ::        (v2.3.8.RELEASE)// 省略一些日志2021-02-20 14:10:02.473  INFO 25240 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''2021-02-20 14:10:02.486  INFO 25240 --- [  restartedMain] com.nobody.Application                   : Started Application in 1.871 seconds (JVM running for 2.598)--- CommandLineRunner in Application...--- Application main has start...

此时可能有人会问,run 方法的 String... args 参数是干嘛用的,它的值又是哪里来的?其实它就是 main 方法的 String[] args 参数,我们在 main 和 run 方法打印输出看看。

@SpringBootApplicationpublic class Application implements CommandLineRunner {
public static void main(String[] args) {
System.out.println("--- Application main begin to start..."); System.out.println("main args length=" + args.length + ", args=" + Arrays.toString(args)); SpringApplication.run(Application.class, args); System.out.println("--- Application main has start..."); } @Override public void run(String... args) throws Exception {
System.out.println("--- CommandLineRunner in Application..."); System.out.println("run args length=" + args.length + ", args=" + Arrays.toString(args)); }}

然后启动先配置下应用启动时传的参数,如下:

在这里插入图片描述
启动服务,启动日志如下,验证正确。

--- Application main begin to start...main args length=2, args=[Mr.nobody, Cx330]  .   ____          _            __ _ _ /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/  ___)| |_)| | | | | || (_| |  ) ) ) )  '  |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot ::        (v2.3.8.RELEASE)// 省略一些日志--- CommandLineRunner in Application...run args length=2, args=[Mr.nobody, Cx330]--- Application main has start...

三、ApplicationRunner 接口说明

其实 ApplicationRunner 接口和 CommandLineRunner 接口作用一样的,唯一不同的是 args 参数。CommandLineRunner 的 run 方法参数是 String… args,而 ApplicationRunner 的 run 方法参数是 ApplicationArguments args。

@SpringBootApplicationpublic class Application implements ApplicationRunner {
public static void main(String[] args) {
System.out.println("--- Application main begin to start..."); System.out.println("main args length=" + args.length + ", args=" + Arrays.toString(args)); SpringApplication.run(Application.class, args); System.out.println("--- Application main has start..."); } @Override public void run(ApplicationArguments args) throws Exception {
System.out.println("--- CommandLineRunner in Application..."); System.out.println("run args optionNames=" + args.getOptionNames() + ", sourceArgs=" + Arrays.toString(args.getSourceArgs()) + ", name=" + args.getOptionValues("name")); }}

在这里插入图片描述

服务启动后,启动日志如下:

--- Application main begin to start...main args length=2, args=[--name=Mr.nobody, --age=18]  .   ____          _            __ _ _ /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/  ___)| |_)| | | | | || (_| |  ) ) ) )  '  |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot ::        (v2.3.8.RELEASE)// 省略一些日志--- CommandLineRunner in Application...run args optionNames=[name, age], sourceArgs=[--name=Mr.nobody, --age=18], name=[Mr.nobody]--- Application main has start...

三、执行顺序

如果在启动服务的时候需要初始化很多资源,并且要保证初始化资源相互之间有序,那如何保证不同的 CommandLineRunner 的执行顺序呢? 如果定义了多个 CommandLineRunner,可以实现 Ordered 接口,或者使用 @Order 注解。我们可以指定 Order 里的值,数字越小越早执行。

而且,CommandLineRunner 和 ApplicationRunner 是可以共存的。

@SpringBootApplication// 注解指定执行顺序@Order(0)public class Application implements CommandLineRunner {
public static void main(String[] args) {
System.out.println("--- Application main begin to start..."); SpringApplication.run(Application.class, args); System.out.println("--- Application main has start..."); } @Override public void run(String... args) throws Exception {
System.out.println("--- CommandLineRunner in Application, Order=0 ..."); }}
@Component// 注解指定执行顺序@Order(1)public class MyCommandLineRunner1 implements CommandLineRunner {
@Override public void run(String... args) throws Exception {
System.out.println("--- CommandLineRunner in MyCommandLineRunner1, Order=1 ..."); }}
@Component// @Order(2)// 实现 Ordered 接口,并且重写 getOrder 方法public class MyCommandLineRunner2 implements CommandLineRunner, Ordered {
@Override public void run(String... args) throws Exception {
System.out.println("--- CommandLineRunner in MyCommandLineRunner2, Order=2 ..."); } // 指定执行顺序 @Override public int getOrder() {
return 2; }}
@Component@Order(3)public class MyApplicationRunner implements ApplicationRunner {
@Override public void run(ApplicationArguments args) throws Exception {
System.out.println("--- ApplicationRunner in MyApplicationRunner, Order=3 ..."); }}

启动服务,启动日志如下:

--- Application main begin to start...  .   ____          _            __ _ _ /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/  ___)| |_)| | | | | || (_| |  ) ) ) )  '  |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot ::        (v2.3.8.RELEASE)// 省略一些日志--- CommandLineRunner in Application, Order=0 ...--- CommandLineRunner in MyCommandLineRunner1, Order=1 ...--- CommandLineRunner in MyCommandLineRunner2, Order=2 ...--- ApplicationRunner in MyApplicationRunner, Order=3 ...--- Application main has start...

四、注意事项

  1. 应用启动时,即 Spring 容器加载完后,才执行 run 方法,并且整个应用生命周期只会执行一次。
  2. 因为 Spring 容器加载完后,才执行 run 方法,所以我们可以在 CommandLineRunner 接口里注入其他依赖(例如 @Autowired 注入),并且使用它们。
  3. run 方法如果抛异常,会导致应用启动失败。如果允许,可以使用 try catch 块处理。
  4. 如果实现 CommandLineRunner 或 ApplicationRunner 接口的类,没有将其注入到 Spring 容器管理(例如没有在类上添加 @Component 注解),是不起作用的。

此演示项目已上传到Github,如有需要可自行下载,欢迎 Star 。

转载地址:https://javalib.blog.csdn.net/article/details/113881768 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:学会这些Logback高级知识点,程序日志性能提高几十倍(上)
下一篇:【Spring系列】使用InitializingBean和DisposableBean来管理bean的生命周期

发表评论

最新留言

表示我来过!
[***.240.166.169]2024年04月29日 03时01分12秒