前言
有时候我们需要在应用启动时执行一些代码片段,这些片段可能是仅仅是为了记录 log,也可能是在启动时检查与安装证书 ,诸如上述业务要求我们可能会经常碰到
Spring Boot 提供了至少 5 种方式用于在应用启动时执行代码。我们应该如何选择?本文将会逐步解释与分析这几种不同方式
CommandLineRunner
CommandLineRunner 是一个接口,通过实现它,我们可以在 Spring 应用成功启动之后 执行一些代码片段
- @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]);
- }
- }
- }
- }
当 Spring Boot 在应用上下文中找到 CommandLineRunner bean,它将会在应用成功启动之后调用 run() 方法,并传递用于启动应用程序的命令行参数
通过如下 maven 命令生成 jar 包:
- mvn clean package
通过终端命令启动应用,并传递参数:
- java -jar springboot-application-startup-0.0.1-SNAPSHOT.jar --foo=bar --name=rgyb
查看运行结果:
到这里我们可以看出几个问题:
不要使用 @Order 太多
看到 order 这个 "黑科技" 我们会觉得它可以非常方便将启动逻辑按照指定顺序执行,但如果你这么写,说明多个代码片段是有相互依赖关系的,为了让我们的代码更好维护,我们应该减少这种依赖使用
小结
如果我们只是想简单的获取以空格分隔的命令行参数,那 MyCommandLineRunner 就足够使用了
ApplicationRunner
上面提到,通过命令行启动并传递参数,MyCommandLineRunner 不能解析参数,如果要解析参数,那我们就要用到 ApplicationRunner 参数了
- @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"));
- }
- }
重新打 jar 包,运行如下命令:
- java -jar springboot-application-startup-0.0.1-SNAPSHOT.jar --foo=bar,rgyb
运行结果如下:
到这里我们可以看出:
2. 在重写的 run() 方法上有 throws Exception 标记,Spring Boot 会将 CommandLineRunner 作为应用启动的一部分,如果运行 run() 方法时抛出 Exception,应用将会终止启动
3. ApplicationRunner 也可以使用 @Order 注解进行排序,从启动结果来看,它与 CommandLineRunner 共享 order 的顺序,稍后我们通过源码来验证这个结论
小结
如果我们想获取复杂的命令行参数时,我们可以使用 ApplicationRunner
ApplicationListener
如果我们不需要获取命令行参数时,我们可以将启动逻辑绑定到 Spring 的 ApplicationReadyEvent 上
- @Slf4j
- @Component
- @Order(0)
- public class MyApplicationListener implements ApplicationListener
{ - @Override
- public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
- log.info("MyApplicationListener is started up");
- }
- }
运行程序查看结果:
到这我们可以看出:
小结
如果我们不需要获取命令行参数,我们可以通过 ApplicationListener
如果你看过我之前写的 Spring Bean 生命周期三部曲:
那么你会对下面两种方式非常熟悉了
@PostConstruct
创建启动逻辑的另一种简单解决方案是提供一种在 bean 创建期间由 Spring 调用的初始化方法。我们要做的就只是将 @PostConstruct 注解添加到方法中:
- @Component
- @Slf4j
- @DependsOn("myApplicationListener")
- public class MyPostConstructBean {
- @PostConstruct
- public void testPostConstruct(){
- log.info("MyPostConstructBean");
- }
- }
查看运行结果:
从上面运行结果可以看出:
2. 相反,它将在依赖于它的所有 bean 被初始化之后被调用,如果要添加人为的依赖关系并由此创建一个排序,则可以使用 @DependsOn 注解(虽然可以排序,但是不建议使用,理由和 @Order 一样)
小结
@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 区别
2. 所以 InitializingBean.afterPropertiesSet 解决方案比使用 @PostConstruct 更安全,因为如果我们依赖尚未自动注入的 @Autowired 字段,则 @PostConstruct 方法可能会遇到 NullPointerExceptions
小结
如果我们使用构造函数注入,则这两种解决方案都是等效的
源码分析
请打开你的 IDE (重点代码已标记注释):
MyCommandLineRunner 和 ApplicationRunner 是在何时被调用的呢?
打开 SpringApplication.java 类,里面有 callRunners 方法
- private void callRunners(ApplicationContext context, ApplicationArguments args) {
- List
- //从上下文获取 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-...)
灵魂追问
在写 Spring Bean 生命周期时就有朋友问我与之相关的问题,显然他们在概念上有一些含混,所以,仔细理解上面的问题将会帮助你加深对 Spring Bean 生命周期的理解
欢迎关注我的公众号 「日拱一兵」,趣味原创解析Java技术栈问题,将复杂问题简单化,将抽象问题图形化落地
如果对我的专题内容感兴趣,或抢先看更多内容,欢迎访问我的博客 dayarch.top
文章标题:一张图帮你记忆,SpringBoot应用在启动阶段执行代码的几种方式
分享路径:http://www.shufengxianlan.com/qtweb/news45/473445.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联