学习方法之少废话:吹牛、装逼、叫大哥。
做者:A哥(YourBatman)
公众号:BAT的乌托邦(ID:BAT-utopia)
文末是否有彩蛋:有
各位小伙伴你们好,我是A哥。本文属总结性文章,对总览Spring Boot
生命周期非常重要,建议点在看、转发“造福”更多小伙伴。java
我最近不是在写Spring Cloud深度剖析的相关专栏麽,最近有收到小伙伴发过来一些问题,经过这段时间收集到的反馈,总结了一下有一个问题很是集中:那即是对Spring Boot应用SpringApplication
的生命周期、事件的理解。有句话我不是常常挂嘴边说的麽,你对Spring Framework有多了解决定了你对Spring Boot有多了解,你对Spring Boot的了解深度又会制约你去了解Spring Cloud,一环扣一环。所以此问题反馈比较集中是在清理之中的~react
为什么在Spring Boot中生命周期事件机制如此重要?原因很简单:Spring Cloud父容器是由该生命周期事件机制来驱动的,而它仅仅是一个典型表明。Spring Cloud构建在Spring Boot之上,它在此基础上构建并添加了一些“Cloud”功能。应用程序事件ApplicationEvent
以及监听ApplicationListener
是Spring Framework提供的扩展点,Spring Boot对此扩展点利用得很是充分和深刻,而且还衍生出了很是多“子”事件类型,甚至自成体系。从ApplicationEvent
衍生出来的子事件类型很是多,例如JobExecutionEvent、RSocketServerInitializedEvent、AuditApplicationEvent...
web
本文并不会对每一个子事件分别介绍(也并没有必要),而是集中火力主攻Spring Boot最为重要的一套事件机制:SpringApplication生命周期的事件体系。spring
本文将以SpringApplication
的启动流程/生命周期各时期发出的Event事件为主线,结合每一个生命周期内完成的大事记介绍,真正实现一文让你总览Spring Boot的全貌,这对你深刻理解Spring Boot,以及整合进Spring Cloud都将很是重要。json
为表诚意,本文一开始便把SpringApplication
生命周期事件流程图附上,而后再精细化讲解各个事件的详情。bootstrap
话外音:赶时间的小伙伴能够拿图走人😁,但不建议白嫖哟
因为不一样版本、类路径下存在不一样包时结果会存在差别,不指明版本的文章都是不够负责任的。所以对导包/版本状况做出以下说明:缓存
Spring Boot
:2.2.2.RELEASE。有且仅导入spring-boot-starter-web
和spring-boot-starter-actuator
Spring Cloud
:Hoxton.SR1。有且仅导入spring-cloud-context
(注意:并不是spring-cloud-starter,并不含有spring-cloud-commons哦)总的来讲:本例导包是很是很是“干净”的,这样在流程上才更有说服力嘛~tomcat
它是和SpringApplication
生命周期有关的全部事件的父类,@since 1.0.0。app
public abstract class SpringApplicationEvent extends ApplicationEvent { private final String[] args; public SpringApplicationEvent(SpringApplication application, String[] args) { super(application); this.args = args; } public SpringApplication getSpringApplication() { return (SpringApplication) getSource(); } public final String[] getArgs() { return this.args; } }
它是抽象类,扩展自Spring Framwork的ApplicationEvent
,确保了事件和应用实体SpringApplication
产生关联(固然还有String[] args
)。它有以下实现子类(7个):spring-boot
每一个事件都表明着SpringApplication不一样生命周期所处的位置,下面分别进行讲解。
@since 1.5.0,并不是1.0.0就有的哦。不过如今几乎没有人用1.5如下的版本了,因此可当它是标准事件。
SpringApplication
实例已实例化:new SpringApplication(primarySources)
它在实例化阶段完成了以下几件“大”事:
webApplicationType
、main方法所在类ApplicationContextInitializer
上下文初始化器- 给字段listeners赋值:拿到SPI方式配置的`ApplicationListener`应用监听器
- 注意:在此阶段(早期阶段)不要过多地使用它的内部状态,由于它可能在生命周期的后期被修改(话外音:使用时需谨慎)
此时,SpringApplicationRunListener
已实例化:它经过SPI方式指定org.springframework.boot.SpringApplicationRunListener=org.springframework.boot.context.event.EventPublishingRunListener
。
EventPublishingRunListener
已经实例化了,所以在后续的事件发送中,均可以触发对应的监听器的执行默认状况下,有4个监听器监听ApplicationStartingEvent
事件:
LoggingApplicationListener
:@since 2.0.0。对日志系统抽象LoggingSystem
执行实例化以及初始化以前的操做,默认使用的是基于Logback的LogbackLoggingSystem
BackgroundPreinitializer
:启动一个后台进行对一些类进行预热。如ValidationInitializer、JacksonInitializer...
,由于这些组件有第一次惩罚的特色(而且首次初始化均还比较耗时),因此使用后台线程先预热效果更佳DelegatingApplicationListener
:它监听的是ApplicationEvent
,而实际上只会ApplicationEnvironmentPreparedEvent
到达时生效,因此此处忽略LiquibaseServiceLocatorApplicationListener
:略总结:此事件节点结束时,SpringApplication
完成了一些实例化相关的动做:本实例实例化、本实例属性赋值、日志系统实例化等。
@since 1.0.0。该事件节点是最为重要的一个节点之一,由于对于Spring应用来讲,环境抽象Enviroment
简直过重要了,它是最为基础的元数据,决定着程序的构建和走向,因此构建的时机是比较早的。
ApplicationArguments
里面ConfigurableEnvironment
的实现类,而且填入值:Profiles配置和Properties属性,默认内容以下(注意,这只是初始状态,后面还会改变、添加属性源,实际见最后的截图):==发送ApplicationEnvironmentPreparedEvent事件,触发对应的监听器的执行==
bindToSpringApplication(environment)
:把环境属性中spring.main.xxx = xxx
绑定到当前的SpringApplication实例属性上,如经常使用的spring.main.allow-bean-definition-overriding=true
会被绑定到当前SpringApplication
实例的对应属性上默认状况下,有9个监听器监听ApplicationEnvironmentPreparedEvent
事件:
BootstrapApplicationListener
:来自SC。优先级最高,用于启动/建立Spring Cloud的应用上下文。须要注意的是:到此时SB的上下文ApplicationContext
还并无建立哦。这个流程“嵌套”特别像Bean初始化流程:初始化Bean A时,遇到了Bean B,就须要先去完成Bean B的初始化,再回头来继续完成Bean A的步骤。
说明:在建立SC的应用的时候,使用的也是SpringApplication#run()
完成的(非web),所以也会走下一整套SpringApplication的生命周期逻辑,因此请你务必区分。
bootstrap
,使用的也是ConfigFileApplicationListener
完成的加载/解析LoggingSystemShutdownListener
:来自SC。对LogbackLoggingSystem
先清理,再从新初始化一次,效果同上个事件,至关于从新来了一次,毕竟如今有Enviroment环境里嘛ConfigFileApplicationListener
:@since 1.0.0。它也许是最重要的一个监听器。作了以下事情:
EnvironmentPostProcessor
实例,而且排好序。须要注意的是:ConfigFileApplicationListener
也是个EnvironmentPostProcessor
,会参与排序哦- **排好序后**,分别一个个的执行`EnvironmentPostProcessor`(@since 1.3.0,并不是一开始就有),介绍以下: - `SystemEnvironmentPropertySourceEnvironmentPostProcessor`:@since 2.0.0。把`SystemEnvironmentPropertySource`替换为其子类`OriginAwareSystemEnvironmentPropertySource`(属性值带有Origin来源),仅此而已 - `SpringApplicationJsonEnvironmentPostProcessor`:@since 1.3.0。把环境中`spring.application.json=xxx`值解析成为一个`MapPropertySource`属性源,而后放进环境里面去(属性源的位置是作了处理的,通常不用太关心) - 能够看到,SB是直接支持JSON串配置的哦。Json解析参见:`JsonParser` - `CloudFoundryVcapEnvironmentPostProcessor`:@since 1.3.0。略 - `ConfigFileApplicationListener`:@since 1.0.0(它比EnvironmentPostProcessor先出现的哦)。加载`application.properties/yaml`等外部化配置,解析好后放进环境里(这应该是最为重要的)。 - 外部化配置默认的优先级为:`"classpath:/,classpath:/config/,file:./,file:./config/"`。当前工程下的config目录里的application.properties优先级最高,当前工程类路径下的application.properties优先级最低 - 值得强调的是:bootstrap.xxx也是由它负责加载的,处理规则同样 - `DebugAgentEnvironmentPostProcessor`:@since 2.2.0。处理和`reactor`测试相关,略
AnsiOutputApplicationListener
:@since 1.2.0。让你的终端(能够是控制台、能够是日志文件)支持Ansi彩色输出,使其更具可读性。固然前提是你的终端支持ANSI显示。参考类:AnsiOutput
。你可经过spring.output.ansi.enabled = xxx
配置,可选值是:DETECT/ALWAYS/NEVER
,通常不动便可。另外,针对控制台能够单独配置:spring.output.ansi.console-available = true/false
LoggingApplicationListener
:@since 2.0.0。根据Enviroment环境完成initialize()
初始化动做:日志等级、日志格式模版等
ClasspathLoggingApplicationListener
:@since 2.0.0。用于把classpath路径以log.debug()
输出,略
Arrays.toString(((URLClassLoader) classLoader).getURLs())
,也就是说每一个.jar里都属于classpath的范畴BackgroundPreinitializer
:本事件达到时无动做DelegatingApplicationListener
:执行经过外部化配置context.listener.classes = xxx,xxx
的监听器们,而后把该事件广播给他们,关心此事件的监听器执行
spring.factories
里,更具弹性FileEncodingApplicationListener
:检测当前系统环境的file.encoding和spring.mandatory-file-encoding设置的值是否同样,若是不同则抛出异常若是不配置spring.mandatory-file-encoding则不检查总结:此事件节点结束时,Spring Boot的环境抽象Enviroment已经准备完毕,但此时其上下文ApplicationContext
还没有建立,可是Spring Cloud的应用上下文(引导上下文)已经所有初始化完毕哦,因此SC管理的外部化配置也应该都进入到了SB里面。以下图所示(这是基本上算是Enviroment的最终态了):
小提示:SC配置的优先级是高于SB管理的外部化配置的。例如针对spring.application.name
这个属性,若bootstrap里已配置了值,再在application.yaml里配置其实就无效了,所以生产上建议不要写两处。
@since 2.1.0,很是新的一个事件。当SpringApplication的上下文ApplicationContext
准备好后,对单例Bean们实例化之前,发送此事件。因此此事件又可称为:contextPrepared
事件。
printBanner(environment)
:打印Banner图,默认打印的是Spring Boot字样
spring.main.banner-mode = xxx
来控制Banner的输出,可选值为CONSOLE/LOG/OFF
,通常默认就好默认在类路径下放置一个banner.txt
文件,可实现自定义Banner。关于更多自定义方式,如使用图片、gif等,本处不作过多介绍
根据是不是web环境、是不是REACTIVE等,用空构造器建立出一个ConfigurableApplicationContext
上下文实例(由于使用的是空构造器,因此不会立马“启动”上下文)
既然上下文实例已经有了,那么就开始对它进行一些参数的设置:
ApplicationContextInitializer
上下文初始化器,而且排序好后挨个执行它(这个很重要),默认有以下截图这些初始化器此时要执行:
下面对这些初始化器分别作出简单介绍:
1. `BootstrapApplicationListener.AncestorInitializer`:来自SC。用于把SC容器设置为SB容器的父容器。固然实际操做委托给了此方法:`new ParentContextApplicationContextInitializer(this.parent).initialize(context)`去完成 2. `BootstrapApplicationListener.DelegatingEnvironmentDecryptApplicationInitializer`:来自SC。代理了下面会提到的`EnvironmentDecryptApplicationInitializer`,也就是说在此处就会先执行,用于提早解密Enviroment环境里面的属性,如相关URL等 3. `PropertySourceBootstrapConfiguration`:来自SC。重要,和配置中心相关,**若想自定义配置中心必须了解它**。主要做用是`PropertySourceLocator`属性源定位器,我会把它放在配置中心章节详解 4. `EnvironmentDecryptApplicationInitializer`:来自SC。属性源头经过上面加载回来了,经过它来实现解密 - 值得注意的是:它被执行了两次哦~ 5. `DelegatingApplicationContextInitializer`:和上面的`DelegatingApplicationListener`功能相似,支持外部化配置`context.initializer.classes = xxx,xxx` 6. `SharedMetadataReaderFactoryContextInitializer`:略 7. `ContextIdApplicationContextInitializer`:@since 1.0.0。设置应用ID -> `applicationContext.setId()`。默认取值为`spring.application.name`,再为application,再为自动生成 8. `ConfigurationWarningsApplicationContextInitializer`:@since 1.2.0。对错误的配置进行**警告**(不会终止程序),以warn()日志输出在控制台。默认内置的只有对包名的检查:若你扫包含有`"org.springframework"/"org"`这种包名就警告 - 若你想自定义检查规则,请实现`Check`接口,而后... 9. `RSocketPortInfoApplicationContextInitializer`:@since 2.2.0。暂略 10. `ServerPortInfoApplicationContextInitializer`:@since 2.0.0。将本身做为一个监听器注册到上下文`ConfigurableApplicationContext`里,专门用于监听`WebServerInitializedEvent`事件(非SpringApplication的生命周期事件) - 该事件有两个实现类:`ServletWebServerInitializedEvent`和`ReactiveWebServerInitializedEvent`。发送此事件的时机是`WebServer`已启动完成,因此已经有了监听的端口号 - 该监听器作的事有两个: - `"local." + getName(context.getServerNamespace()) + ".port"`做为key(默认值是`local.server.port`),value是端口值。这样能够经过@Value来获取到本机端口了(但貌似端口写0的时候,SB在显示上有个小bug) - 做为一个属性源`MapPropertySource`放进环境里,属性源名称为:`server.ports`(由于一个server是能够监听多个端口的,因此这里用复数) - `ConditionEvaluationReportLoggingListener`:将`ConditionEvaluationReport`报告(自动配置中哪些匹配了,哪些没匹配上)写入日志,固然只有`LogLevel#DEBUG`时才会输出(注意:这不是日志级别哦,应该叫报告级别)。如你配置`debug=true`就开启了此自动配置类报告 - 槽点:它明明是个初始化器,为毛命名为Listener?
默认状况下,有2个监听器监听ApplicationContextInitializedEvent
事件:
BackgroundPreinitializer
:本事件达到时无动做DelegatingApplicationListener
:本事件达到时无动做总结:此事件节点结束时,完成了应用上下文ApplicationContext的准备工做,而且执行全部注册的上下文初始化器ApplicationContextInitializer
。可是此时,单例Bean是仍旧尚未初始化,而且WebServer也尚未启动
@since 1.0.0。截止到上个事件ApplicationContextInitializedEvent
,应用上下文ApplicationContext充其量叫实例化好了,可是还剩下很重要的事没作,这即是本周期的内容。
把applicationArguments、printedBanner等都做为一个Bean放进Bean工厂里(所以你就能够@Autowired注入的哦)
若lazyInitialization = true
延迟初始化,那就向Bean工厂放一个:new LazyInitializationBeanFactoryPostProcessor()
LazyInitializationExcludeFilter
接口指定的排除在外)所有.setLazyInit(true);
延迟初始化根据primarySources和allSources,交给BeanDefinitionLoader
(SB提供的实现)实现加载Bean的定义信息,它支持4种加载方式(4种源):
AnnotatedBeanDefinitionReader
-> 基于注解XmlBeanDefinitionReader
-> 基于xml配置GroovyBeanDefinitionReader
-> Groovy文件ClassPathBeanDefinitionScanner
-> classpath中加载默认状况下,有6个监听器监听ApplicationContextInitializedEvent
事件:
CloudFoundryVcapEnvironmentPostProcessor
:略ConfigFileApplicationListener
:向上下文注册一个new PropertySourceOrderingPostProcessor(context)
。它的做用是:Bean工厂结束后对环境里的属性源进行重排序 -> 把名字叫defaultProperties
的属性源放在最末位
SpringApplication#setDefaultProperties
API方式放进来的,通常不会使用到,留个印象便可LoggingApplicationListener
:由于这时已经有Bean工厂了嘛,因此它作的事是:向工厂内放入Bean
BackgroundPreinitializer
:本事件达到时无动做RestartListener
:SC提供。把当前最新的上下文缓存起来而已,目前并未发现有实质性做用,可忽略DelegatingApplicationListener
:本事件达到时无动做总结:此事件节点结束时,应用上下文ApplicationContext初始化完成,该赋值的赋值了,Bean定义信息也已所有加载完成。可是,单例Bean尚未被实例化,web容器依旧还没启动。
@since 2.0.0。截止到此,应用已经准备就绪,而且经过监听器、初始化器等完成了很是多的工做了,但仍旧剩下被认为最为重要的初始化单例Bean动做还没作、web容器(如Tomcat)还没启动,这即是这个周期所要作的事。
启动Spring容器:AbstractApplicationContext#refresh()
,这个步骤会作不少事,好比会实例化单例Bean
WebServer
也随之完成启动,成功监听到对应端口(们) Started Application in xxx seconds (JVM running for xxx)
callRunners()
:依次执行容器内配置的ApplicationRunner/CommandLineRunner
的Bean实现类,支持sort排序
ApplicationRunner
:@since 1.3.0,入参是ApplicationArguments
,先执行(推荐使用)CommandLineRunner
:@since 1.0.0,入参是String... args
,后执行(不推荐使用)默认状况下,有3个监听器监听ApplicationStartedEvent
事件:
TomcatMetricsBinder
:@since 2.1.0。和监控相关:将你的tomcat指标信息TomcatMetrics
绑定到MeterRegistry
,从而就能收集到相关指标了总结:此事件节点结束时,SpringApplication
的生命周期到这一步,正常的启动流程就所有完成了。也就说Spring Boot应用能够正常对对外提供服务了。
@since 1.3.0。该事件所处的生命周期可认为基本同ApplicationStartedEvent
,仅是在其后执行而已,二者中间并没有其它特别的动做,可是监听此事件的监听器们仍是蛮重要的。
同上。
默认状况下,有4个监听器监听ApplicationStartedEvent
事件:
SpringApplicationAdminMXBeanRegistrar
:当此事件到达时,告诉Admin Spring应用已经ready,可使用啦。RefreshEventListener
:当此事件到达时,告诉Spring应用已经ready了,接下来即可以执行ContextRefresher.refresh()
喽总结:此事件节点结束时,应用已经完彻底全的准备好了,而且也已经完成了相关组件的周知工做。
SpringApplication
是有可能在启动的时候失败(如端口号已被占用),固然任何一步骤遇到异常时交给SpringApplication#handleRunFailure()
方法来处理,这时候也会有对应的事件发出。
当SpringApplication
在启动时抛出异常:多是端口绑定、也多是你自定义的监听器你写了个bug等,就会“可能”发送此事件。
ExitCodeEvent
事件(非生命周期事件)ApplicationFailedEvent
事件,触发对应的监听器的执行默认状况下,有6个监听器监听ApplicationStartedEvent
事件:
LoggingApplicationListener
:执行loggingSystem.cleanUp()
清理资源ClasspathLoggingApplicationListener
:输出一句debug日志:Application failed to start with classpath: ...
ConditionEvaluationReportLoggingListener
:自动配置输出报告,输出错误日志呗:特别方便你查看和错误定位
BootstrapApplicationListener.CloseContextOnFailureApplicationListener
:执行context.close()总结:此事件节点结束时,会作一些释放资源的操做。通常状况下:咱们并不须要监听到此事件
关于SpringApplication
的生命周期体系的介绍就到这了,相信经过此“万字长文”你能体会到A哥的用心。翻了翻市面上的相关文章,本文Almost能够保证是总结得最到位的,让你经过一文即可从大的方面基本掌握Spring Boot,这无论是你使用SB,仍是后续自行扩展、精雕细琢SB,以及去深刻了解Spring Cloud均由很是重要的意义,但愿对你有帮助,谢谢你的三连。