关注微信公众号【
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...
复制代码
四、注意事项
- 应用启动时,即 Spring 容器加载完后,才执行 run 方法,并且整个应用生命周期只会执行一次。
- 因为 Spring 容器加载完后,才执行 run 方法,所以我们可以在 CommandLineRunner 接口里注入其他依赖(例如 @Autowired 注入),并且使用它们。
- run 方法如果抛异常,会导致应用启动失败。如果允许,可以使用 try catch 块处理。
- 如果实现 CommandLineRunner 或 ApplicationRunner 接口的类,没有将其注入到 Spring 容器管理(例如没有在类上添加 @Component 注解),是不起作用的。
此演示项目已上传到Github,如有需要可自行下载,欢迎 Star 。
github.com/LucioChn/sp…
关注微信公众号【
Java之言
】,更多干货文章
和学习资料
,助你放弃编程之路!
近期评论