不懂SpringApplication生命周期事件?那就等于不会Spring Boot嘛(中)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 不懂SpringApplication生命周期事件?那就等于不会Spring Boot嘛(中)

监听此事件的监听器们


默认情况下,有9个监听器监听ApplicationEnvironmentPreparedEvent事件:


image.png


1.BootstrapApplicationListener:来自SC。优先级最高,用于启动/创建Spring Cloud的应用上下文。需要注意的是:到此时SB的上下文ApplicationContext还并没有创建哦。这个流程“嵌套”特别像Bean初始化流程:初始化Bean A时,遇到了Bean B,就需要先去完成Bean B的初始化,再回头来继续完成Bean A的步骤。

说明:在创建SC的应用的时候,使用的也是SpringApplication#run()完成的(非web),因此也会走下一整套SpringApplication的生命周期逻辑,所以请你务必区分。

   1.特别是这种case会让“绝大多数”初始化器、监听器等执行多次,若你有那种只需要执行一次的需求(比如只想让SB容器生命周期内执行,SC生命周期不执行),请务必自行处理,否则会被执行多次而带来不可预知的结果

   2.SC应用上下文读取的外部化配置文件名默认是:bootstrap,使用的也是ConfigFileApplicationListener完成的加载/解析


2.LoggingSystemShutdownListener:来自SC。对LogbackLoggingSystem先清理,再重新初始化一次,效果同上个事件,相当于重新来了一次,毕竟现在有Enviroment环境里嘛


3.ConfigFileApplicationListener:@since 1.0.0。它也许是最重要的一个监听器。做了如下事情:

   1.加载SPI配置的所有的EnvironmentPostProcessor实例,并且排好序。需要注意的是:ConfigFileApplicationListener也是个EnvironmentPostProcessor,会参与排序哦


image.png


排好序后,分别一个个的执行EnvironmentPostProcessor(@since 1.3.0,并非一开始就有),介绍如下:

1.SystemEnvironmentPropertySourceEnvironmentPostProcessor:@since 2.0.0。把SystemEnvironmentPropertySource替换为其子类OriginAwareSystemEnvironmentPropertySource(属性值带有Origin来源),仅此而已


2.SpringApplicationJsonEnvironmentPostProcessor:@since 1.3.0。把环境中spring.application.json=xxx值解析成为一个MapPropertySource属性源,然后放进环境里面去(属性源的位置是做了处理的,一般不用太关心)

            1.可以看到,SB是直接支持JSON串配置的哦。Json解析参见:JsonParser


3.CloudFoundryVcapEnvironmentPostProcessor:@since 1.3.0。略


4.ConfigFileApplicationListener:@since 1.0.0(它比EnvironmentPostProcessor先出现的哦)。加载application.properties/yaml等外部化配置,解析好后放进环境里(这应该是最为重要的)。

         1.外部化配置默认的优先级为:"classpath:/,classpath:/config/,file:./,file:./config/"。当前工程下的config目录里的application.properties优先级最高,当前工程类路径下的application.properties优先级最低

         2.值得强调的是:bootstrap.xxx也是由它负责加载的,处理规则一样

   5.DebugAgentEnvironmentPostProcessor:@since 2.2.0。处理和reactor测试相关,略


4.AnsiOutputApplicationListener:@since 1.2.0。让你的终端(可以是控制台、可以是日志文件)支持Ansi彩色输出,使其更具可读性。当然前提是你的终端支持ANSI显示。参考类:AnsiOutput。你可通过spring.output.ansi.enabled = xxx配置,可选值是:DETECT/ALWAYS/NEVER,一般不动即可。另外,针对控制台可以单独配置:spring.output.ansi.console-available = true/false


5.LoggingApplicationListener:@since 2.0.0。根据Enviroment环境完成initialize()初始化动作:日志等级、日志格式模版等

   1.值得注意的是:它这步相当于在ApplicationStartingEvent事件基础上进一步完成了初始化(上一步只是实例化)


6.ClasspathLoggingApplicationListener:@since 2.0.0。用于把classpath路径以log.debug()输出,略

   1.值得注意的是:classpath类路径是有N多个的Arrays.toString(((URLClassLoader) classLoader).getURLs()),也就是说每个.jar里都属于classpath的范畴

   2.当然喽,你需要注意Spring在处理类路径时:classpath和classpath*的区别~,这属于基础知识


7.BackgroundPreinitializer:本事件达到时无动作


8.DelegatingApplicationListener:执行通过外部化配置context.listener.classes = xxx,xxx的监听器们,然后把该事件广播给他们,关心此事件的监听器执行

   1.这麽做的好处:可以通过属性文件外部化配置监听器,而不一定必须写在spring.factories里,更具弹性

   2.外部化配置的执行优先级,还是相对较低的,到这里才给与执行嘛


9FileEncodingApplicationListener:检测当前系统环境的file.encoding和spring.mandatory-file-encoding设置的值是否一样,如果不一样则抛出异常如果不配置spring.mandatory-file-encoding则不检查


总结:此事件节点结束时,Spring Boot的环境抽象Enviroment已经准备完毕,但此时其上下文ApplicationContext还没有创建,但是Spring Cloud的应用上下文(引导上下文)已经全部初始化完毕哦,所以SC管理的外部化配置也应该都进入到了SB里面。如下图所示(这是基本上算是Enviroment的最终态了):

image.png

小提示:SC配置的优先级是高于SB管理的外部化配置的。例如针对spring.application.name这个属性,若bootstrap里已配置了值,再在application.yaml里配置其实就无效了,因此生产上建议不要写两处。


ApplicationContextInitializedEvent:上下文已实例化

@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等,本处不做过多介绍
  • 小建议:别花里胡哨搞个佛祖在那。让它能自动打印输出当前应用名,这样才是最为实用,最高级的(但需要你定制化开发,并且支持可配置,最好对使用者无感,属于一个common组件)
  • 根据是否是web环境、是否是REACTIVE等,用空构造器创建出一个ConfigurableApplicationContext上下文实例(因为使用的是空构造器,所以不会立马“启动”上下文)


  • SERVLET -> AnnotationConfigServletWebServerApplicationContext
  • REACTIVE -> AnnotationConfigReactiveWebServerApplicationContext
  • 非web环境 -> AnnotationConfigApplicationContext(SC应用的容器就是使用的它)
  • 既然上下文实例已经有了,那么就开始对它进行一些参数的设置:


  • 首先最重要的便是把已经准备好的环境Enviroment环境设置给它
  • 设置些beanNameGenerator、resourceLoader、ConversionService等组件
  • 实例化所有的ApplicationContextInitializer上下文初始化器,并且排序好后挨个执行它(这个很重要),默认有如下截图这些初始化器此时要执行:



image.png


  1. 下面对这些初始化器分别做出简单介绍:
  2. BootstrapApplicationListener.AncestorInitializer:来自SC。用于把SC容器设置为SB容器的父容器。当然实际操作委托给了此方法:new ParentContextApplicationContextInitializer(this.parent).initialize(context)去完成
  3. BootstrapApplicationListener.DelegatingEnvironmentDecryptApplicationInitializer:来自SC。代理了下面会提到的EnvironmentDecryptApplicationInitializer,也就是说在此处就会先执行,用于提前解密Enviroment环境里面的属性,如相关URL等
  4. PropertySourceBootstrapConfiguration:来自SC。重要,和配置中心相关,若想自定义配置中心必须了解它。主要作用是PropertySourceLocator属性源定位器,我会把它放在配置中心章节详解
  5. EnvironmentDecryptApplicationInitializer:来自SC。属性源头通过上面加载回来了,通过它来实现解密
  6. 值得注意的是:它被执行了两次哦~
  7. DelegatingApplicationContextInitializer:和上面的DelegatingApplicationListener功能类似,支持外部化配置context.initializer.classes = xxx,xxx
  8. SharedMetadataReaderFactoryContextInitializer:略
  9. ContextIdApplicationContextInitializer:@since 1.0.0。设置应用ID -> applicationContext.setId()。默认取值为spring.application.name,再为application,再为自动生成
  10. ConfigurationWarningsApplicationContextInitializer:@since 1.2.0。对错误的配置进行警告(不会终止程序),以warn()日志输出在控制台。默认内置的只有对包名的检查:若你扫包含有"org.springframework"/"org"这种包名就警告
  11. 若你想自定义检查规则,请实现Check接口,然后…
  12. RSocketPortInfoApplicationContextInitializer:@since 2.2.0。暂略
  13. ServerPortInfoApplicationContextInitializer:@since 2.0.0。将自己作为一个监听器注册到上下文ConfigurableApplicationContext里,专门用于监听WebServerInitializedEvent事件(非SpringApplication的生命周期事件)
  14. 该事件有两个实现类:ServletWebServerInitializedEvent和ReactiveWebServerInitializedEvent。发送此事件的时机是WebServer已启动完成,所以已经有了监听的端口号
  15. 该监听器做的事有两个:
  16. "local." + getName(context.getServerNamespace()) + ".port"作为key(默认值是local.server.port),value是端口值。这样可以通过@Value来获取到本机端口了(但貌似端口写0的时候,SB在显示上有个小bug)
  17. 作为一个属性源MapPropertySource放进环境里,属性源名称为:server.ports(因为一个server是可以监听多个端口的,所以这里用复数)
  • ConditionEvaluationReportLoggingListener:将ConditionEvaluationReport报告(自动配置中哪些匹配了,哪些没匹配上)写入日志,当然只有LogLevel#DEBUG时才会输出(注意:这不是日志级别哦,应该叫报告级别)。如你配置debug=true就开启了此自动配置类报告
  • 槽点:它明明是个初始化器,为毛命名为Listener?
  • 发送ApplicationContextInitializedEvent事件,触发对应的监听器的执行


监听此事件的监听器们


默认情况下,有2个监听器监听ApplicationContextInitializedEvent事件:


image.png


  1. BackgroundPreinitializer:本事件达到时无动作
  2. DelegatingApplicationListener:本事件达到时无动作


总结:此事件节点结束时,完成了应用上下文ApplicationContext的准备工作,并且执行所有注册的上下文初始化器ApplicationContextInitializer。但是此时,单例Bean是仍旧还没有初始化,并且WebServer也还没有启动


ApplicationPreparedEvent:上下文已准备好


@since 1.0.0。截止到上个事件ApplicationContextInitializedEvent,应用上下文ApplicationContext充其量叫实例化好了,但是还剩下很重要的事没做,这便是本周期的内容。


完成的大事记


  • 把applicationArguments、printedBanner等都作为一个Bean放进Bean工厂里(因此你就可以@Autowired注入的哦)
  • 比如:有了Banner这个Bean,你可以在你任何想要输出的地方输出一个Banner,而不仅仅是启动时只会输出一次了
  • 若lazyInitialization = true延迟初始化,那就向Bean工厂放一个:new LazyInitializationBeanFactoryPostProcessor()
  • 该处理器@since 2.2.0。该处理器的作用是:对所有的Bean(通过LazyInitializationExcludeFilter接口指定的排除在外)全部.setLazyInit(true);延迟初始化
  • 根据primarySources和allSources,交给BeanDefinitionLoader(SB提供的实现)实现加载Bean的定义信息,它支持4种加载方式(4种源):


  • AnnotatedBeanDefinitionReader -> 基于注解
  • XmlBeanDefinitionReader -> 基于xml配置
  • GroovyBeanDefinitionReader -> Groovy文件
  • ClassPathBeanDefinitionScanner -> classpath中加载
  • (不同的源使用了不同的load加载方式)
  • 发送ApplicationPreparedEvent事件,触发对应的监听器的执行


监听此事件的监听器们

默认情况下,有6个监听器监听ApplicationContextInitializedEvent事件:



image.png


  1. CloudFoundryVcapEnvironmentPostProcessor:略
  2. ConfigFileApplicationListener:向上下文注册一个new PropertySourceOrderingPostProcessor(context)。它的作用是:Bean工厂结束后对环境里的属性源进行重排序 -> 把名字叫defaultProperties的属性源放在最末位
  3. 该属性源是通过SpringApplication#setDefaultProperties API方式放进来的,一般不会使用到,留个印象即可
  4. LoggingApplicationListener:因为这时已经有Bean工厂了嘛,所以它做的事是:向工厂内放入Bean
  5. “springBootLoggingSystem” -> loggingSystem
  6. “springBootLogFile” -> logFile
  7. “springBootLoggerGroups” -> loggerGroups
  8. BackgroundPreinitializer:本事件达到时无动作
  9. RestartListener:SC提供。把当前最新的上下文缓存起来而已,目前并未发现有实质性作用,可忽略
  10. DelegatingApplicationListener:本事件达到时无动作


总结:此事件节点结束时,应用上下文ApplicationContext初始化完成,该赋值的赋值了,Bean定义信息也已全部加载完成。但是,单例Bean还没有被实例化,web容器依旧还没启动。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
24天前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
38 2
|
2月前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
65 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
2月前
|
Java 开发者 Spring
Spring bean的生命周期详解!
本文详细解析Spring Bean的生命周期及其核心概念,并深入源码分析。Spring Bean是Spring框架的核心,由容器管理其生命周期。从实例化到销毁,共经历十个阶段,包括属性赋值、接口回调、初始化及销毁等。通过剖析`BeanFactory`、`ApplicationContext`等关键接口与类,帮助你深入了解Spring Bean的管理机制。希望本文能助你更好地掌握Spring Bean生命周期。
117 1
|
2月前
|
Java 开发者 Spring
Spring bean的生命周期详解!
本文详细介绍了Spring框架中的核心概念——Spring Bean的生命周期,包括实例化、属性赋值、接口回调、初始化、使用及销毁等10个阶段,并深入剖析了相关源码,如`BeanFactory`、`DefaultListableBeanFactory`和`BeanPostProcessor`等关键类与接口。通过理解这些核心组件,读者可以更好地掌握Spring Bean的管理和控制机制。
94 1
|
2月前
|
缓存 NoSQL Java
Springboot自定义注解+aop实现redis自动清除缓存功能
通过上述步骤,我们不仅实现了一个高度灵活的缓存管理机制,还保证了代码的整洁与可维护性。自定义注解与AOP的结合,让缓存清除逻辑与业务逻辑分离,便于未来的扩展和修改。这种设计模式非常适合需要频繁更新缓存的应用场景,大大提高了开发效率和系统的响应速度。
70 2
|
5月前
|
Java Spring 容器
Spring Boot 启动源码解析结合Spring Bean生命周期分析
Spring Boot 启动源码解析结合Spring Bean生命周期分析
109 11
|
4月前
|
Java Spring 供应链
Spring 框架事件发布与监听机制,如魔法风暴席卷软件世界,开启奇幻编程之旅!
【8月更文挑战第31天】《Spring框架中的事件发布与监听机制》介绍了Spring中如何利用事件发布与监听机制实现组件间的高效协作。这一机制像城市中的广播系统,事件发布者发送消息,监听器接收并响应。通过简单的示例代码,文章详细讲解了如何定义事件类、创建事件发布者与监听器,并确保组件间松散耦合,提升系统的可维护性和扩展性。掌握这一机制,如同拥有一把开启高效软件开发大门的钥匙。
50 0
|
4月前
|
前端开发 Java 开发者
|
4月前
|
Java Spring
Spring的Bean生命周期中@PostConstruct注解
【8月更文挑战第3天】在Spring框架中,`@PostConstruct`注解标示Bean初始化完成后立即执行的方法。它在依赖注入完成后调用,适用于资源加载、属性设置等初始化操作。若方法中抛出异常,可能影响Bean初始化。与之对应,`@PreDestroy`注解的方法则在Bean销毁前执行,用于资源释放。
157 0
|
5月前
|
存储 设计模式 Java
Spring Boot中的事件溯源模式
Spring Boot中的事件溯源模式