当前位置: 首页 > 科技观察

一张图帮你记住,SpringBoot应用程序在启动阶段是如何执行代码的

时间:2023-03-12 07:36:58 科技观察

前言有时候我们需要在应用程序启动的时候执行一些代码片段。启动时检查和安装证书,比如上面的业务需求,我们可能经常会遇到SpringBoot提供了至少5种方式在应用程序启动时执行代码。我们应该如何选择?本文将逐步讲解和分析这些不同的方式CommandLineRunnerCommandLineRunner是一个接口,通过实现它,我们可以在Spring应用启动成功后执行一些代码片段@Slf4j@Component@Order(2)publicclassMyCommandLineRunnerimplementsCommandLineRunner{@Overridepublicvoidrun(String...args)throwsException{log.info("MyCommandLineRunnerorderis2");if(args.length>0){for(inti=0;i数组2.重写的run()方法上有throwsException标记,和SpringBoot将使用CommandLineRunner作为应用程序启动的一部分。如果运行run()方法时抛出异常,应用程序将终止启动3.ApplicationRunner也可以使用@Order注释进行排序。从启动结果来看,和CommandLineRunner的顺序是一样的。后面我们会通过源码来验证这个结论。总结如果我们想获取复杂的命令行参数,可以使用ApplicationRunnerApplicationListener,如果不需要获取命令行参数,可以将启动逻辑绑定到Spring的ApplicationReadyEvent@Slf4j@Component@Order(0)publicclassMyApplicationListenerimplementsApplicationListener{@OverridepublicvoidonApplicationEvent(ApplicationReadyEventapplicationReadyEvent){log.info("MyApplicationListenerisstarted"}}运行程序查看结果:此时我们可以看到:当且仅当应用程序就绪时才会触发ApplicationReadyEvent,即使是上面的Listener也会执行完本文提到的所有解决方案后触发最后的结论请看后面的代码,我用Order(0)来标记,显然ApplicationListener也可以用这个注解排序,按编号排序应该是第一个执行但是这个顺序只是用于同类型的ApplicationListener之间的排序,并不共享上面提到的ApplicationRunners和CommandLineRunners的排序。启动逻辑,通过它我们还可以获取SpringBoot支持的配置属性环境变量参数如果你看过我之前写的SpringBean生命周期三部曲:SpringBean生命周期的由来SpringBean生命周期的由来到底是SpringAware是什么?那么下面这两种方式你就会很熟悉了。@PostConstruct另一个创建启动逻辑的简单解决方案是提供一个初始化方法,在bean创建期间由Spring调用。我们只需要在方法中添加@PostConstruct注解即可:@Component@Slf4j@DependsOn("myApplicationListener")publicclassMyPostConstructBean{@PostConstructpublicvoidtestPostConstruct(){log.info("MyPostConstructBean");}}从上面查看运行结果:运行结果可见:Spring创建bean后(启动前),会立即调用@PostConstruct注解标记的方法,所以我们不能使用@Order注解随意排序,因为它可能依赖@自动装配到我们bean中的其他Springbean中。2.相反,它会在所有依赖于它的bean都被初始化之后被调用。如果想添加人工依赖,创建序列,可以使用@DependsOn注解(虽然可以排序,但不推荐。原因同@Order)bean,所以它应该只用于这个单个bean的初始化逻辑;InitializingBean和@PostConstruct方案很相似,我们可以实现InitializingBean接口,让Spring调用一个初始化方法:}查看运行结果:从上面的运行结果来看,我们得到和@PostConstruct效果一样,但是@PostConstruct和afterPropertiesSet还是有区别的。AfterPropertiesSet,顾名思义,“设置属性后”,当调用这个方法时,bean的所有属性都已经被Spring填充了。如果我们在某些属性上使用@Autowired(正常操作应该使用构造函数注入),那么Spring会在调用afterPropertiesSet之前将bean注入到这些属性中。但是@PostConstruct没有这些属性填充限制2.所以InitializingBean.afterPropertiesSet方案比使用@PostConstruct更安全,因为@PostConstruct方法如果依赖没有自动注入的@Autowired字段可能会遇到NullPointerExceptions进行函数注入,这些两种方案相当于源码分析。请打开你的IDE(关键代码已经标注注释):MyCommandLineRunner和ApplicationRunner是什么时候调用的?打开SpringApplication.java类,里面有callRunners方法privatevoidcallRunners(ApplicationContextcontext,ApplicationArgumentsargs){Listrunners=newArrayList<>();//从context中获取ApplicationRunner类型的beanrunners.addAll(context.getBeansOfType(ApplicationRunner.class).values());//从context.addAll(context.getBeansOfType(CommandLineRunner.class).values());//对两者进行排序,这就是顺序的原因两者可以共享AnnotationAwareOrderComparator.sort(runners);//遍历调用它for(Objectrunner:newLinkedHashSet<>(runners)){if(runnerinstanceofApplicationRunner){callRunner((ApplicationRunner)runner,args);}if(runnerinstanceofCommandLineRunner){callRunner((CommandLineRunner)亚军,参数);}}}强烈建议完整的看一遍SpringApplication.java的所有代码。SpringBoot的启动流程和原理可以从这门课中找到一些答案。查看原文:https://dayarch.top/p/spring-...)魂问上面程序的结果,在@PostConstruct方法之前调用了afterPropertiesSet方法,但这和我们调用是一样的SpringBean生命周期的起源顺序恰恰相反,你知道为什么吗?MyPostConstructBean通过@DependsOn("myApplicationListener")依赖于MyApplicationListener。为什么前者先调用后者呢?为什么不建议@AutowiredFormDependencyInjection在写SpringBean生命周期的时候,有朋友问过我。显然他们在概念上有些歧义,所以仔细理解以上问题,会帮助你加深对SpringBean的理解。对于生命周期的理解,请关注我的公众号“日工兵兵”,这是一个Java技术栈问题的有趣原创分析,将复杂问题简单化,抽象问题图形化实现。如果您对我的专题内容感兴趣,或者先观看更多内容,欢迎访问我的博客dayarch.top