阅读 834

[SpringBoot] 源码解读(二)

SpringBoot 源码

接上回, 这篇文章继续解读 SpringApplication 实例的 run 方法.

⚠️注意,不同版本的 SpringBoot 的源码行数可能不同, 这篇文章使用的是 2.4.1 版本

SpringApplication 实例的 run 方法

方法参数

  • args, 传入的值为 java main 方法的参数

方法执行

  • 311 行, 312 行: 新建一个 StopWatch 实例并调用 start 方法

这个 StopWatch 主要是用来统计程序启动所花费的时间

  • 313 行: 设置 JVM 系统属性 java.awt.headless

  • 314 行: 创建一个 SpringApplicationRunListeners 实例

    • 创建这个实例时调用到了上一篇讲到的 getSpringFactoriesInstances 方法, 这里不再详述.
    • 那么这个 SpringApplicationRunListeners 是干啥的呢, 其实他只是一个非常简单的 Wrapper 类, 只有两个属性, 一个是 Log, 另一个是一个 SpringApplicationRunListener 的列表.

接下来对于这个实例的操作, 基本上都相当于对这些列表里的对象的操作.

  • 316 行: 调用 listeners.starting() 方法, 其实就是循环调用每一个 SpringApplicationRunListener 对象的 starting 方法

    • 这个 starting 方法是一个接口中的默认(default)方法, 这里不再解释为什么接口里面还会有方法体.
    • 这个方法主要用来做一些程序启动早期的一些初始化动作, 比如它的一个实现类 EventPublishingRunListener 就是在这个方法的实现中广播了一个 ApplicationStartingEvent 类型的事件.
  • 319 行: 将 main 方法的参数转换成一个 ApplicationArguments 对象, 用于更方便的解析和获取参数中的值

  • 320 行: 创建并配置一个 ConfigurableEnvironment 对象

    • 根据当前应用类型(NONE / SERVLET/ REACTIVE)创建对应的 ConfigurableEnvironment 实现类的对象
      • 配置这个对象的几个属性

        • 实例化一个 ConversionService 对象并赋值
      • 配置 PropertySources 属性, 这一步实际做的工作主要还是将 main 方法的参数作为一个 property source 添加到框架的 property source 列表中(注意这里其实还没有开始读 applicaiton.properties 或者 application.yml 文件)

        • 配置 Profile 属性, 这一步主要是读取程序启动参数中的 spring.profiles.active 的值, 将参数中设置的的 profile 作为 当前已激活的 profile
      • Attach property source, 其实就是将 application.properties 或者 application.yml 作为一个 property source 加到框 架的 property source 列表中

      • listener.environmentPrepared(), 这一步一般就会广播一个 ApplicationEnvironmentPreparedEvent 类型的事件

    • 将当前的 springappliaction 对象绑定到 environment 的属性 spring.main 上 (这里之后会专门准备一篇文章讲解)
      • 接下来这一步有些奇怪, 对当前 environment 对象做了一个类型检查, 来确保 enviroment 对象符合当前的应用类型, 但是刚才的 environment 对象就是根据当前应用类型创建的, 难道说这个 environment 对象会被修改? (这里之后再仔细研究)
      • 由于有了上面的这一步奇怪的操作, 还需要重复一下上面的一个操作, 即: Attach property source
  • 321 行: 设置 JVM 属性 spring.beaninfo.ignore

  • 322 行: 打印 banner 信息

    • 这里就是打印 banner.txt 的内容了, 没什么好说的
  • 323 行: 创建应用上下文

    • 根据应用类型( NONE / SERVLET / REACTIVE) 选择相应的上下文 class
    • 通过反射机制实例化并初始化上下文对象
  • 325 行: 准备应用上下文 (到了大头了)

    • 将 environment 对象赋给 context 对象的属性
    • postProcessApplicationContext
      • 注册一个单例的 bean name generator 对象
      • 设置几个属性值: resouceLoader, classLoader, conversionService
    • applyInitializers (我发现有些时候直接写方法名, 比自己翻译出来更精确)
      • 先是做了一个看起来有点奇怪的类型转换, 然后遍历每个 ApplicationContextInitializer 接口的实现类, 调用它们的 initialize 方法
  • 326 行: 刷新应用上下文

    • 注册一个 shutdownHook 线程,如果 JVM 意外终止了,这个线程会把当前的上下文关闭
    • 调用上下文对象的 refresh 方法
      • 以 ServletWebServerApplicationContext 为例
      • 方法中用 synchronized 关键字进行了加锁控制,保证 refresh 方法执行时的安全
      • 刷新前的准备
        • 初始化 property resource, 对于 servlet 类型的应用来说,就是初始化 servletContextInitParams 和 servletConfigInitParams
        • 验证是否所有必须存在(required)的参数都存在
      • 刷新的 bean factory
      • 准备 bean factory
        • 忽略一些 *Aware.class 接口类的依赖, 如 ApplicationContextAware 等
        • 注册一些依赖到 bean factory 中, 如把当前的上下文对象注册为 ApplicationContext.class 的单例对象
        • 添加 ApplicationListenerDetector 用于检测 ApplicationListener 的内部类
        • 注册一些单例bean,如 Environment, SystemProperty 等
      • postProcess bean factory
        • 主要是创建 bean, 执行 bean post processors
      • 初始化 message source 相关的 bean
      • 初始化 ApplicationEventMulticaster 相关的 bean
      • onRefresh
        • 这是一个模板方法,默认是一个空实现,对于 Servlet 类型的应用来说,创建 Tomcat 服务器就是在这个方法里面执行的,这一步执行完,Tomcat 服务器就起来了
      • 注册 ApplicationListener 的实现类
      • 结束 bean factory 的初始化,到这里,所有的非延迟加载的单例bean都初始化完成了
      • 完成应用上下文的刷新,主要是广播一个 ContextRefreshedEvent 类型的事件
  • 327 行: 调用了一个空的 afterRefresh 的方法,应该是给之后可能要增加的操作留一个钩子

  • 330 行: 打印一行日志,显示启动当前应用所花的时间

    • Started SpringbootLearningApplication in 35.39 seconds (JVM running for 35.92)
  • 332 行: 广播一个 ApplicationStartedEvent 类型的事件

  • 333 行: 调用所有 ApplicationRunner 和 CommandLineRunner 对象的 run 方法

    • 先获取所有的 ApplicationRunner 和 CommandLineRunner 对象的集合
    • 对这个集合中的对象进行排序,排序依据仍然是根据 Order 和 Priority 的值升序排列
    • 遍历集合,调用每个对象的 run 方法
      • 注意,这里是同步调用对象的 run 方法,而不是开启一个线程,所以不要这这些 run 方法里面做一个耗时的操作,否则会阻塞后面的的 run 方法的运行
      • 再说一遍,不是异步执行的,是同步执行的
  • 341 行: 如果上面最后一步 333 行执行完毕,没有异常,那么在这一行上,就会广播一个 ApplicationReadyEvent 类型的事件,到此,整个 run 方法执行完毕,程序启动完成


总结一下:

上一篇讲的是 SpringApplication 的构造方法,这一篇讲的是通过构造方法构造出来的实例的 run 方法。

这个 run 方法里面做了很多事情,大概如下:

  • 读取运行环境相关的配置项,如系统的环境变量,启动参数,配置文件,servlet 初始化参数等等
  • 创建并刷新上下文,这个是 Spring 的核心,在这一步中,会进行 bean 的实例化,初始化操作
  • 创建 WebServer,一般情况下是 Tomcat
  • 触发相应的生命周期事件,比如 ApplicationStartedEvent AppicationReadyEvent 等
  • 调用所有 ApplicationRunner 和 CommandLineRunner 的对象方法

另外,这两篇,尤其是这一篇写的有些粗糙,目的在于快速的把程序的启动过程搞清楚,具体每个步骤里带的细节,会另外分专门的文章来解读。

文章分类
后端
文章标签