【Spring系列】应用启动后回调机制CommandLineRunner和ApplicationRunner接口

895 阅读4分钟

关注微信公众号【Java之言】,更多干货文章学习资料,助你放弃编程之路!

一、前言

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

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

二、CommandLineRunner 接口说明

package org.springframework.boot;

@FunctionalInterface
public interface CommandLineRunner {
    void run(String... args) throws Exception;
}

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

@SpringBootApplication
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...");
    }
}

启动日志如下,证明确实会在应用启动时,即 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 方法打印输出看看。

@SpringBootApplication
public 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。

@SpringBootApplication
public 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 。
github.com/LucioChn/sp…


关注微信公众号【Java之言】,更多干货文章学习资料,助你放弃编程之路! 在这里插入图片描述