Foreword
Sometimes we need to perform some code snippets when the application starts, these fragments may be simply to record log, it may be the time to start checking and installing the certificate , such as the above business requirements we may often encounter
Spring Boot provides at least 5 ways to execute code when the application starts. How should we choose? This article will explain step by step and analyze this in several different ways
CommandLineRunner
CommandLineRunner
Is an interface by implementing it, we can Spring 应用成功启动之后
execute some code snippets
@Slf4j
@Component
@Order(2)
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
log.info("MyCommandLineRunner order is 2");
if (args.length > 0){
for (int i = 0; i < args.length; i++) {
log.info("MyCommandLineRunner current parameter is: {}", args[i]);
}
}
}
}
When Spring Boot found in the application context CommandLineRunner
bean, which will be called after the successful launch of the application run()
method and pass command line arguments to start the application
Maven jar package generated by the command:
mvn clean package
Command to start the application by the terminal, and pass parameters:
java -jar springboot-application-startup-0.0.1-SNAPSHOT.jar --foo=bar --name=rgyb
View the results:
Here we can see a few questions:
- Command line parameters passed has not been resolved, but only shows that we passed in the string contents
--foo=bar
,--name=rgyb
we canApplicationRunner
resolve, we see later - In rewriting
run()
there are methodologicalthrows Exception
mark, Spring Boot will beCommandLineRunner
applied as part of the launch, if you runrun()
throws Exception time method, the application will terminate start - We added on the class
@Order(2)
notes, when there is moreCommandLineRunner
time, will follow@Order
from small to large (digital of course, can also be used in the plural) annotation of digital
⚠️ Do not use
@Order
too muchOrder to see this "black technology" we will find it very easy to start logic executed in a specified order, but if you are so write, illustrate various snippets are interdependent relationship, in order to make our code more maintainable, we should reduce this reliance on the use
summary
If we just want to get a simple command-line parameters separated by spaces, it MyCommandLineRunner
is sufficient to use the
ApplicationRunner
As mentioned above, the command line to start and pass parameters, MyCommandLineRunner
you can not resolve the argument, if you want to parse parameters, then we have to use the ApplicationRunner
argument of
@Component
@Slf4j
@Order(1)
public class MyApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
log.info("MyApplicationRunner order is 1");
log.info("MyApplicationRunner Current parameter is {}:", args.getOptionValues("foo"));
}
}
Re-jar packaging, run the following command:
java -jar springboot-application-startup-0.0.1-SNAPSHOT.jar --foo=bar,rgyb
Results are as follows:
Here we can see:
- With
MyCommandLineRunner
similar, butApplicationRunner
can be run by the method ofApplicationArguments
parsing the command line parameter object, and each parameter which can have multiple values, since thegetOptionValues
method returns ListArray - In rewriting
run()
there are methodologicalthrows Exception
mark, Spring Boot will beCommandLineRunner
applied as part of the launch, if you runrun()
throws Exception time method, the application will terminate start ApplicationRunner
You can also use@Order
annotations to sort, starting from the results, itCommandLineRunner
order to share the order, later on we verify this conclusion by source
summary
If we want to get complicated command-line parameters, we can use ApplicationRunner
ApplicationListener
If we do not get command-line arguments, we can start to bind to the logic of Spring ApplicationReadyEvent
on
@Slf4j
@Component
@Order(0)
public class MyApplicationListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
log.info("MyApplicationListener is started up");
}
}
Run the program to see the results:
With this we can see:
ApplicationReadyEvent
If and only if was only triggered after the application is ready, and even said that will only be triggered after the above article in the Listener to say that all solutions are implemented, the final conclusions please see later- I use the code
Order(0)
marked clearly ApplicationListener also possible to sort by this comment, by numerical order size, should be executed first. However, this order only for the same type of ordering between the ApplicationListener, previously mentionedApplicationRunners
andCommandLineRunners
sorted do not share
summary
If we do not get command-line arguments, we can ApplicationListener<ApplicationReadyEvent>
, we can get through it to create some global startup logic configuration properties environment variable parameters Spring Boot Support
If you read my writing before the Spring Bean Life Cycle trilogy:
- Spring Bean Life Cycle of origin
- Edge Spring Bean lifecycle of the best
- Spring Aware in the end is what?
Then you will have the following two ways very familiar
@PostConstruct
Create a boot logical Another simple solution is to provide a method for initializing calls during the Spring created in the bean. We need to do is to @PostConstruct
add annotations to methods:
@Component
@Slf4j
@DependsOn("myApplicationListener")
public class MyPostConstructBean {
@PostConstruct
public void testPostConstruct(){
log.info("MyPostConstructBean");
}
}
View the results:
As seen from the above results run:
- Spring bean has been created after ( before the start ), it will immediately call the
@PostConstruct
method annotated markers, so we can not use@Order
annotations to sort them free, because it may depend on@Autowired
insertion into other Spring bean our bean. - Instead, it will be called after all it depends on the bean is initialized, if you want to add artificial dependency and thus create a sort, you can use the
@DependsOn
annotation (although you can sort, but does not recommend the use of the grounds and@Order
the same)
summary
@PostConstruct
方法固有地绑定到现有的 Spring bean,因此应仅将其用于此单个 bean 的初始化逻辑;
InitializingBean
与 @PostConstruct
解决方案非常相似,我们可以实现 InitializingBean
接口,并让 Spring 调用某个初始化方法:
@Component
@Slf4j
public class MyInitializingBean implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
log.info("MyInitializingBean.afterPropertiesSet()");
}
}
查看运行结果:
从上面的运行结果中,我们得到了和 @PostConstruct
一样的效果,但二者还是有差别的
⚠️
@PostConstruct
和afterPropertiesSet
区别
- afterPropertiesSet,顾名思义「在属性设置之后」,调用该方法时,该 bean 的所有属性已经被 Spring 填充。如果我们在某些属性上使用
@Autowired
(常规操作应该使用构造函数注入),那么 Spring 将在调用afterPropertiesSet
之前将 bean 注入这些属性。但@PostConstruct
并没有这些属性填充限制- 所以
InitializingBean.afterPropertiesSet
解决方案比使用@PostConstruct
更安全,因为如果我们依赖尚未自动注入的@Autowired
字段,则@PostConstruct
方法可能会遇到 NullPointerExceptions
小结
如果我们使用构造函数注入,则这两种解决方案都是等效的
源码分析
请打开你的 IDE (重点代码已标记注释):
MyCommandLineRunner
和ApplicationRunner
是在何时被调用的呢?
打开 SpringApplication.java
类,里面有 callRunners
方法
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
//从上下文获取 ApplicationRunner 类型的 bean
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
//从上下文获取 CommandLineRunner 类型的 bean
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
//对二者进行排序,这也就是为什么二者的 order 是可以共享的了
AnnotationAwareOrderComparator.sort(runners);
//遍历对其进行调用
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
强烈建议完整看一下 SpringApplication.java
的全部代码,Spring Boot 启动过程及原理都可以从这个类中找到一些答案
总结
最后画一张图用来总结这几种方式(高清大图请查看原文:https://dayarch.top/p/spring-boot-execute-on-startup.html)
灵魂追问
- 上面程序运行结果,
afterPropertiesSet
方法调用先于@PostConstruct
方法,但这和我们在 Spring Bean 生命周期之缘起 中的调用顺序恰恰相反,你知道为什么吗? MyPostConstructBean
通过@DependsOn("myApplicationListener")
依赖了 MyApplicationListener,为什么调用结果前者先与后者呢?- 为什么不建议
@Autowired
形式依赖注入
在写 Spring Bean 生命周期时就有朋友问我与之相关的问题,显然他们在概念上有一些含混,所以,仔细理解上面的问题将会帮助你加深对 Spring Bean 生命周期的理解
欢迎持续关注公众号:「日拱一兵」
- 前沿 Java 技术干货分享
- 高效工具汇总 | 回复「工具」
- 面试问题分析与解答
- 技术资料领取 | 回复「资料」
以读侦探小说思维轻松趣味学习 Java 技术栈相关知识,本着将复杂问题简单化,抽象问题具体化和图形化原则逐步分解技术问题,技术持续更新,请持续关注......