springboot(2.4.1)源代码分析六springboot启动逻辑总结 置顶!

  |   0 评论   |   0 浏览

  springboot的启动代码拉通看了一遍,逻辑还是很复杂,其中用到了事件驱动,除了springboot本身自带的监听器以外,还可以自定义一系列监听器,还是非常灵活,下面就springboot整个启动逻辑,记录一下关键点,方便后续回顾。下面图是run过程中的几个关键事件:1607555941892534272.png

正文

下面直入正题,从最开始的new记录起:

1、new SpringApplication(primarySources)

  • 根据classpath下的类,判断出WebApplicationType
  • 从META-INF/spring.factories加载所有的BootstrapRegistryInitializer
  • 从META-INF/spring.factories加载所有的ApplicationContextInitializer
  • 从META-INF/spring.factories加载所有的ApplicationListener
  • 从当前执行的栈帧,探测出执行main方法的mainApplicationClass

2、执行SpringApplication的run方法

这个方法就是springboot启动的核心方法

  • 创建DefaultBootstrapContext,引导上下文是作为真正上下文创建前的引导类使用,在真正的上下文创建后这个对象会被释放。
  • 调用所有BootstrapRegistryInitializer的initialize方法,初始化DefaultBootstrapContext,默认没有BootstrapRegistryInitializer,可以自定义引导上下文的初始化器。
  • 设置java.awt.headless模式,默认为true,用于在缺失显示屏、鼠标或者键盘时的系统配置。
  • 从META-INF/spring.factories加载所有的SpringApplicationRunListener,用于接收springboot启动时候的那几个事件,如上图中所示starting、environmentPrepared、contextPrepared、contextLoaded、started、running(ready)、failed
  • 触发监听者的starting事件
  • 创建ApplicationArguments,用来解析程序启动命令行参数

3、执行prepareEnvironment方法准备ConfigurableEnvironment应用环境

这个是run方法中的代码,单独拿出来写是因为很重要。

ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
  • 根据webApplicationType类型,创建对应的ConfigurableEnvironment,目前有三类,基于传统servlet的ApplicationServletEnvironment、基于reactive响应式web编程的ApplicationReactiveWebEnvironment,以及常规java程序ApplicationEnvironment。
  • 加载配置源和Profiles
  • 添加ConfigurationPropertySourcesPropertySource到配置源中,用来桥接 ConfigurationPropertySources类型的资源,ConfigurationPropertySources是什么类型?就是@ConfigurationProperties注解底层使用的封装,获取配置文件中的prefix,和注解对象的类成员变量,递归将配置属性赋值给类成员变量这一类型的资源。
  • 执行SpringApplicationRunListener的environmentPrepared事件,这个事件的监听者有很多,其中有几个关键的监听者:
    • EnvironmentPostProcessorApplicationListener,它又包含spring.factories文件中所有定义的EnvironmentPostProcessor对象,例如添加随机属性处理器、属性命名解析处理器、json配置处理器、以及核心的ConfigDataEnvironmentPostProcessor,这个类是用来判断Springboot运行环境处理器,判断springboot当前运行在哪个环境,参考spring.profiles.active配置。
    • LoggingApplicationListener,初始化日志系统
    • BackgroundPreinitializer,后台资源初始化监听,例如JacksonInitializer、ConversionServiceInitializer、ValidationInitializer等等
    • DelegatingApplicationListener,委派监听,可以把启动事件广播到context.listener.classes配置的自定义监听器上面
  • 将defaultProperties移动到配置源的末尾,使其优先级最低
  • 采用Binder,将spring.main配置的属性绑定到SpringApplication对象上,Binder用来绑定配置属性非常方便。
  • EnvironmentConverter尝试对环境变量Environment进行转换,还是基于webApplicationType类型进行转换(因为最开始那个Environment就是基于webApplicationType类型创建的,不知道为啥还要在这里再判断一次)
  • 再一次添加ConfigurationPropertySourcesPropertySource到配置源中(逻辑同上)

环境准备过后,确定了多个配置变量的获取优先级,这个很重要,如下所示,越前面的优先级越高:

  • ConfigurationPropertySourcesPropertySource {name='configurationProperties'}:
  • SimpleCommandLinePropertySource {name='commandLineArgs'}:命令行参数
  • StubPropertySource {name='servletConfigInitParams'}:servlet初始化参数
  • StubPropertySource {name='servletContextInitParams'}:ServletContext初始化参数
  • PropertiesPropertySource {name='systemProperties'}:JVM的System系统属性
  • OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}:操作系统的环境变量
  • RandomValuePropertySource {name='random'}:随机生成的带random.*前缀的属性
    下面两个是加载的classpath:/config/application.yml或者classpath:/config/application.properties,调试环境的spring.profiles.active=dev
  • OriginTrackedMapPropertySource {name='Config resource 'class path resource [application-dev.yml]' via location 'optional:classpath:/''}
  • OriginTrackedMapPropertySource {name='Config resource 'class path resource [application.yml]' via location 'optional:classpath:/''}

4、执行prepareContext准备上下文

继续执行run中逻辑,中间有一些非核心逻辑,例如打印banner、配置spring.beaninfo.ignore系统属性等等,不细写,接下来就是创建ConfigurableApplicationContext对象,通过applicationContextFactory创建对应的上下文,本文是基于常规的web应用来做的,所以默认上下文是通过AnnotationConfigServletWebServerApplicationContext来创建的,另一个上下文类型是AnnotationConfigReactiveWebServerApplicationContext响应式web的上下文。上下文创建好了过后,就开始执行核心方法prepareContext:

  • 设置上下文的Environment环境变量
  • ApplicationContext前置处理,设置BEAN_NAME_GENERATOR、ResourceLoader、ClassLoader、类型转换器ConversionService
  • 执行所有的ApplicationContextInitializer,这里有几个重要的上下文初始化器要注意:
    • DelegatingApplicationContextInitializer,委派初始化器,可以执行context.initializer.classes配置的自定义初始化器
    • ContextIdApplicationContextInitializer,设置上下文ID,取的是spring.application.name配置的值,如果没有配置,则默认为application
    • ServerPortInfoApplicationContextInitializer,监听WebServerInitializedEvent事件,可以基于server.port配置来设置服务监听端口号
  • 触发监听者contextPrepared事件
  • 广播BootstrapContextClosedEvent事件,关闭引导上下文对象
  • beanFactory注册applicationArguments、printedBanner对象,另外设置allowCircularReferences是否允许循环引用、allowBeanDefinitionOverriding是否允许bean定义被覆盖
  • 如果设置了懒初始化上下文,那么添加LazyInitializationBeanFactoryPostProcessor对象到bean工厂前置处理器中
  • 加载所有资源类,默认就是Application启动类,并且注册Application类到上下文中
  • 触发监听者的contextLoaded事件

5、执行refreshContext方法刷新上下文

这个也是run方法中的核心逻辑,刷新上下文,这里执行了很多方法和逻辑,下面一一列举:

  • 给上下文添加一个shutdownHook
  • 预处理上下文信息:
  1. 记录服务启动是时间戳
  2. 设置当前服务 context 关闭标记(closed),活动标记(active)
  3. 根据需要执行特殊的属性初始化操作。
  4. 校验必须配置属性是否设置成功
  5. 设置早期的事件监听列表 ApplicationListener 列表。
  • 对获取的 BeanFactory 实例对象进行预处理:
  1. 设置 Bean 的类加载器(beanClassLoader)。
  2. 根据配置 spring.spel.ignore 添加表达式解析工具类实例(StandardBeanExpressionResolver)。
  3. 添加属性编辑器注册器(ResourceEditorRegistrar)。
  4. 添加 Aware 接口执行器(ApplicationContextAwareProcessor),并将 Aware的子接口 接口设置为忽略依赖接口。
  5. 注册 指定类型类的实例。BeanFactory.class, beanFactory
  6. 添加实现BeanPostProcessor接口的 Listener 装饰器 (ApplicationListenerDetector)。后续将实现 ApplicationListener 的bena添加到 applicationListeners 列表钟。
  7. 如果配置了loadTimeWeaver实现,则添加实现BeanPostProcessor接口的 LoadTimeWeaverAwareProcessor。后续给实现LoadTimeWeaverAware 的bean设置LoadTimeWeaver实现对象。实际中使用较少。
  8. 将环境变量和系统属性相关的对象实例注册到 单例Bean Map中。
    getEnvironment().getSystemProperties() JVM 启动时相关的环境属性信息
    getEnvironment().getSystemEnvironment() OS 系统的环境变量相关属性信
  • invokeBeanFactoryPostProcessors(beanFactory)

回调 BeanFactoryPostProcessors 实现类的postProcessBeanFactory方法,进行BeanFactory后置处理。

PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()

  1. 根据 PriorityOrdered,Ordered以及常规的 BeanDefinitionRegistryPostProcessor 实现类 顺序依次调用 的 postProcessBeanDefinitionRegistry 方法。
  2. 根据 PriorityOrdered,Ordered以及常规的 BeanFactoryPostProcessors 实现类 顺序依次调用 的postProcessBeanFactory方法。
  • registerBeanPostProcessors(beanFactory)

向 BeanFactory 中注册 BeanPostProcessor 实现类。

PostProcessorRegistrationDelegate.registerBeanPostProcessors
1.从 BeanFactory 取出 beanDefinitionMap 中所有 BeanPostProcessor 实现类
2. 按 PriorityOrdered,Ordered以及nonOrdered 顺序添加到 BeanFactory的beanPostProcessors 列表中。
如果 一个实现类继承 MergedBeanDefinitionPostProcessor 接口时,则添加到列表最后。

  • registerListeners()
    注册 Listeners (列表),广播 earlyApplicationEvents 事件。
    1.将 context 的 applicationListeners (列表) 添加到广播器(applicationEventMulticaster)的applicationListeners(列表)中。
    2.将 BeanFactory 中管理的所有 ApplicationListener 类型Bean添加到广播器(applicationEventMulticaster) applicationListenerBeans(列表)中。
    3.将 earlyApplicationEvents 事件列表发送到广播器(applicationEventMulticaster)。将遍历触发满足监听事件的 ApplicationListener.onApplicationEvent 方法。
  • finishBeanFactoryInitialization(beanFactory)
    BeanFactory 最终的初始化操作
    1. beanFactory 设置 ConversionService 实现Bean。
    2. beanFactory 设置 EmbeddedValueResolver 实现Bean。
    3. 获取 beanFactory 所有 LoadTimeWeaverAware 类型 bean。并创建 bean 实例。
    4. beanFactory 设置 tempClassLoader 为 null。阻止再使用 tempClassLoader
    5. beanFactory 冻结配置 beanDefinitionNames。为后面 批量处理单例 Bean 做准备。
    6. beanFactory 获取所有非懒加载的单例 bean对象。
  • finishRefresh()
    上下文(context) 后续处理逻辑。
  1. 清理资源缓存(resourceCaches)信息
  2. 设置 context 的生命周期处理器 (LifecycleProcessor)。如果没有配置,则使用 DefaultLifecycleProcessor 实现。
  3. 调用 LifecycleProcessor.onRefresh 方法。获取 beanFactory 中所有 Lifecycle 类型bean,按 Phased 分组进行排序,然后遍历执行 Lifecycle.start 方法。
  4. 发布 ContextRefreshedEvent 事件。先向当前上下文(context) 环境发送事件广播。然后向父类发布 ContextRefreshedEvent 事件广播。
  5. 获取 平台 MBeanServer 实现,将 LiveBeansView 和 applicationName 注册到 MBeanServer 中。
  • resetCommonCaches()

清理公共缓存。

1.清理反射中缓存的方法和字段信息。

2.清理注解缓存的信息。

3.清理解析类型相关的缓存信息。

4.清理类加载器相关的信息。

6、收尾逻辑

继续执行run方法剩余逻辑

  • 触发监听者的started方法
  • 执行callRunners
    • 先执行ApplicationRunner
    • 再执行CommandLineRunner
  • 触发监听者的ready(running)方法

以上就是整个springboot的启动逻辑,整个过程还是很复杂,而且很灵活,用了很多函数式编程,看完代码过后也会受到熏陶,记录观摩心得,以便后续回顾。


标题:springboot(2.4.1)源代码分析六springboot启动逻辑总结
作者:michael
地址:https://blog.junxworks.cn/articles/2022/12/27/1672105698192.html