Spring Boot 2 -Spring Boot 如何解决项目启动时初始化资源
我们在开发中可能会有这样的情景。需要在容器启动的时候执行一些内容。比如读取配置文件,数据库连接之类的。SpringBoot给我们提供了两个接口来帮助我们实现这种需求。这两个接口分别为CommandLineRunner
和ApplicationRunner
,这两个接口的Component会在所有Spring Beans 都初始化之后,SpringApplication.run() 之前执行,非常适合在应用程序启动之初进行一些数据初始化的工作。
这两个接口中有一个run方法,我们只需要实现这个方法即可。这两个接口的不同之处在于:ApplicationRunner中run方法的参数为ApplicationArguments,而CommandLineRunner接口中run方法的参数为String数组。下面我写两个简单的例子,来看一下这两个接口的实现。
CommandLineRunner和ApplicationRunner的例子
接下来我们就运用案例测试它如何使用,在测试之前在启动类加两行打印提示,方便我们识别CommandLineRunner
的执行时机。
@SpringBootApplication
public class CommandLineRunnerApplication {
public static void main(String[] args) {
System.out.println("The service to start.");
SpringApplication.run(CommandLineRunnerApplication.class, args);
System.out.println("The service has started.");
}
}
接下来我们直接创建一个类继承CommandLineRunner
,并实现它的run()
方法。
@Component
public class Runner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("The Runner start to initialize ...");
}
}
我们在run()
方法中打印了一些参数来抛光它的执行时机。完成之后启动项目进行测试:
The service to start.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.0.RELEASE)
...
2018-04-21 22:21:34.706 INFO 27016 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2018-04-21 22:21:34.710 INFO 27016 --- [ main] com.neo.CommandLineRunnerApplication : Started CommandLineRunnerApplication in 3.796 seconds (JVM running for 5.128)
The Runner start to initialize ...
The service has started.
根据控制台的打印信息我们可以抛光CommandLineRunner
中的方法会在Spring Boot容器加载之后执行,执行完成后项目启动完成。
同理ApplicationRunner
一样。
ApplicationRunner与CommandLineRunner谁先执行
我们在一个应用中分别实现两个接口
@Component
public class CommandLineRunnerTest implements CommandLineRunner {
@Override
public void run(String... arg0) throws Exception {
System.out.println("CommandLineRunner=====执行开始");
for (int i = 0; i < arg0.length; i++) {
System.out.println(arg0[i]);
}
System.out.println("CommandLineRunner=====执行完毕");
}
}
@Component
public class ApplicationRunnerTest implements ApplicationRunner {
@Override
public void run(ApplicationArguments arg0) throws Exception {
System.out.println("ApplicationRunner=====执行开始");
System.out.println(arg0.getNonOptionArgs());
System.out.println(arg0.getOptionNames());
System.out.println("ApplicationRunner=====执行完成");
}
}
从执行结果来看,ApplicationRunner默认先于CommandLineRunner执行
ApplicationRunner=====执行开始
[]
[name, age]
[lisi]
ApplicationRunner=====执行完成
CommandLineRunner=====执行开始
--name=lisi
--age=12
CommandLineRunner=====执行完毕
两者的区别,以及与main方法的联系
两个接口中都有run方法,负责接收来自应用外部的参数,ApplicationRunner的run方法会将外部参数封装成一个ApplicationArguments对象,而CommandLineRunner接口中run方法的参数则为String数组。
我们再回头看看main方法
@SpringBootApplication
public class AliyunmqApplication {
public static void main(String[] args) {
System.out.println("main方法==========执行开始");
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
System.out.println("main方法==========执行完毕");
SpringApplication.run(AliyunmqApplication.class, args);
}
}
我们看到main方法也是接收一个args 数组参数。
执行发现:
main方法==========执行开始
--name=lisi
--age=12
main方法==========执行完毕
..............
..............
ApplicationRunner=====执行开始
[]
[name, age]
[lisi]
ApplicationRunner=====执行完成
CommandLineRunner=====执行开始
--name=lisi
--age=12
CommandLineRunner=====执行完毕
main方法的arg参数 和 CommandLineRunner方法的arg数组的值是一样的。
总的来看: ApplicationRunner run方法中的ApplicationArguments 对象,对输入参数做了封装,让我们可以使用 get**()的形式获取参数。CommandLineRunner run方法的arg数组则是原装。
多个实现类的执行顺序
我们创造两个CommandLineRunner
的实现类来进行测试:
第一个实现类:
@Component
@Order(1)
public class OrderRunner1 implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("The OrderRunner1 start to initialize ...");
}
}
第二个实现类:
@Component
@Order(2)
public class OrderRunner2 implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("The OrderRunner2 start to initialize ...");
}
}
添加完成之后重新启动,观察执行顺序:
...
The service to start.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.0.0.RELEASE)
...
2018-04-21 22:21:34.706 INFO 27016 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2018-04-21 22:21:34.710 INFO 27016 --- [ main] com.neo.CommandLineRunnerApplication : Started CommandLineRunnerApplication in 3.796 seconds (JVM running for 5.128)
The OrderRunner1 start to initialize ...
The OrderRunner2 start to initialize ...
The Runner start to initialize ...
The service has started.
通过控制台的输出我们发现,添加@Order
注解的实现类最先执行,并且@Order()
里面的值越小启动越早。