前言

在项目中经常会遇到需要在项目启动之前做一些初始化数据的操作,例如初始化线程池、初始化一些脚本等等。但是往往这些操作又需要在 Spring Beans 加载完成之后,因为需要依赖于一些 Spring BeansCommandLineRunner 可以很好的解决这个问题,继承了 ComandLineRunner 接口并且加上了 @Component 注解的类可以在 Spring Beans 都初始化之后,而又在 SpringApplication.run() 之前执行。下面来详细介绍 CommandLineRunner 的使用。

正文

修改启动类,添加两行打印代码,看查看 CommandLineRunner 运行的时机

1
2
3
4
5
6
7
8
@SpringBootApplication
public class TestApplication {
    public static void main(String[] args) {
        System.out.println("This Application run before");
        SpringApplication.run(TestApplication.class, args);
        System.out.println("This Application run after");
    }
}

然后写一个 Runner 类,继承 ComandLineRunner 接口,并实现 run() 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Component
public class Runner implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        for (int i = 0; i < 5; i++) {
            System.out.println("Runner no." + i);
        }
    }
}

注意 :一定要加上 @Component 注解

我们启动应用查看输出,输出顺序如下(省略不重要的)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
This Application run before

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.7.RELEASE)
 
2019-08-29 21:51:55.532  INFO 5576 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-08-29 21:51:55.555  INFO 5576 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-08-29 21:51:55.555  INFO 5576 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.22]
2019-08-29 21:51:55.660  INFO 5576 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-08-29 21:51:55.660  INFO 5576 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1361
Runner no.0
Runner no.1
Runner no.2
Runner no.3
Runner no.4
This Application run after

可以看到 run() 方法确实是在 Spring Beans 都加载完成后,但是又在 SpringApplication.run() 之前执行。

那么如果有多个类继承了 CommandLineRunner 接口并且加上了 @Component 注解,那么执行顺序如何呢?

Spring Boot 提供一个注解 @Order 来解决这个问题,@Order 注解内有一个 value 值,该值设置的越小,则该类越早被执行。

RunnerOrder1.class

1
2
3
4
5
6
7
8
9
@Component
@Order(value = 1)
public class RunnerOrder1 implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println(this.getClass().getName());
    }
}

RunnerOrder2.class

1
2
3
4
5
6
7
8
9
@Component
@Order(value = 2)
public class RunnerOrder2 implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println(this.getClass().getName());
    }
}

执行结果如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
This Application run before

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.7.RELEASE)

2019-08-29 22:06:31.538  INFO 13368 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2019-08-29 22:06:31.566  INFO 13368 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2019-08-29 22:06:31.566  INFO 13368 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.22]
2019-08-29 22:06:31.680  INFO 13368 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2019-08-29 22:06:31.681  INFO 13368 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1617 ms
com.example.mybatisplus.service.component.RunnerOrder1
com.example.mybatisplus.service.component.RunnerOrder2
This Application run after

那么如果两个类的执行优先级一致呢?本人的测试结果是:对于执行优先度同一级的类,按类的全限定类名的字符串排序,字典序越小的越早被执行。

对于上述的需求,还有一种方法是实现 ApplicationRunner 接口,用法与上述一致,唯一的区别就是 ApplicationRunner 的 run() 方法传入的参数是 ApplicationArguments,而 CommandLineRunner 的 run() 方法传入的是 String 数组,如果你想要得到更加详细传入信息就可以使用 ApplicationRunner