• 面试官问Spring 启动流程,把这篇文章甩给他!


    大家好,我是三友~~

    今天来扒一扒Spring在启动过程中核心的12个步骤

    之所以来写这篇文章,主要是来填坑的

    之前在三万字盘点Spring 9大核心基础功能这篇文章的末尾中给自己挖了一个坑,提了一嘴有机会要写这么一篇文章

    但是由于Spring启动过程并不复杂,所以后面就没写了

    不过,好巧不巧,刚刚好有兄弟来催更了,那么此时这个机会就来了,这篇文章也就有了

    公众号:三友的java日记

    前言

    Spring启动时候整个入口是这么一个方法

    AbstractApplicationContext#refresh

    总共有12个方法,也就是启动时的核心步骤

    AbstractApplicationContext有众多实现,这里我选择SpringBoot Web应用默认的实现来讲

    AnnotationConfigServletWebServerApplicationContext

    AnnotationConfigServletWebServerApplicationContext类图
    AnnotationConfigServletWebServerApplicationContext类图

    对应的SpringBoot版本为 2.2.5.RELEASE

    高版本refresh方法会多一些日志相关的代码,这里为了方便讲解,就使用这个版本

    所以后面本文提到的所有子类的方法实现、重写都是指AnnotationConfigServletWebServerApplicationContext及其父类

    本文主要是讲一些启动的步骤,具体的很多技术实现细节、技术点这里就不过多赘述

    如果有疑问的,可以查看三万字盘点Spring 9大核心基础功能这篇文章或者公众号菜单栏中关于Spring的文章,基本上都能找到答案

    prepareRefresh

    prepareRefresh整个刷新的一个步骤,这个步骤是做启动的一个准备操作

    ApplicationContext刚创建出来,什么也没有,所以需要做一些准备

    首先是做一些状态位的变更,表明开始启动了或者刷新了

    后面一行

    initPropertySources

    initPropertySources是一个模板方法,本身是一个空实现,是给子类用的

    我们的这个子类就重写了initPropertySources方法

    会将Servlet相关的配置加入到Environment中,这样我们就能从Environment中获取到Servlet相关的配置了

    再后面一行

    getEnvironment().validateRequiredProperties()

    这行代码就是校验一些必要的配置属性,我们可以通过ConfigurableEnvironment来设置哪些属性是必要的,默认是没有必要的

    所以prepareRefresh就是做了一些前置操作,准备好一些属性配置相关的东西,后面的其它环节,比如说生成Bean时可能需要用到这些配置

    obtainFreshBeanFactory

    这一步骤是刷新BeanFactory并且获取BeanFactory

    refreshBeanFactory() 和 getBeanFactory() 都是抽象方法,由子类来实现的

    而子类的实现其实很简单,就是给beanFactory设置一个id和返回beanFactory

    beanFactory就是下面这个玩意

    并且创建对象的ApplicationContext对象的时候就创建了,类型为

    DefaultListableBeanFactory

    所以从这就可以看出来,虽然说BeanFactory是一个接口,有非常多的实现

    但是实际情况下,真正使用的就是DefaultListableBeanFactory

    并且DefaultListableBeanFactory其实算是BeanFactory唯一真正的实现

    除此之外,还可以得出一个结论,ApplicationContext中有一个BeanFactory(DefaultListableBeanFactory)

    prepareBeanFactory

    上一步骤获取到了BeanFactory,但是这个BeanFactory仅仅就是刚刚new出来的,什么也没有

    所以当前步骤就是对BeanFactory做一些配置工作

    前三行代码

    先给BeanFactory设置了一个ClassLoader,因为BeanFactory是用来创建Bean,需要加载Bean class对象

    然后设置了一个BeanExpressionResolver,这个是用来解析SpEL表达式的

    然后添加了一个PropertyEditorRegistrar,也就是

    ResourceEditorRegistrar

    这个的作用就是为BeanFactory添加一堆跟资源相关的PropertyEditor

    ResourceEditorRegistrar核心实现方法
    ResourceEditorRegistrar核心实现方法

    PropertyEditor之前说过,就是进行类型转换的,将一个字符串转成对应的类型

    接下来这几行

    这里主要是添加了一个BeanPostProcessor,也就是

    ApplicationContextAwareProcessor

    BeanPostProcessor我们都知道会在Bean的生命周期阶段进行回调,是Bean的生命周期一个核心的环节

    ApplicationContextAwareProcessor这个是用来处理Bean生命周期中的Aware回调有关

    当你的Bean实现这些接口的时候,在创建的时候Spring会回调这些接口,传入对应的对象

    而后面的这行代码

    beanFactory.ignoreDependencyInterface(EnvironmentAware.class);

    意思是说,如果你的Bean想注入一个EnvironmentAware对象

    @Resource
    private EnvironmentAware environmentAware;
    

    这是不允许的

    因为很简单,注入一个EnvironmentAware对象,没有实际的意义

    后面的其它几行代码也都是这个意思

    再接下来这几行

    这跟上面的ignoreDependencyInterface作用相反

    他是来设置依赖注入时Bean的类型所对应的对象

    比如说这行代码

    beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);

    这行代码的意思就是当你需要注入一个Bean类型为BeanFactory.class类型的时候

    @Resource
    private BeanFactory beanFactory;
    

    那么实际注入的就是方法第二个参数beanFactory,也就是上面获取的DefaultListableBeanFactory对象

    同理,注入ResourceLoader、ApplicationEventPublisher、ApplicationContext时,其实注入的对象都是this,也就是当前的ApplicationContext对象

    再再接下来这几行

    最开始又添加了一个BeanPostProcessor

    ApplicationListenerDetector

    这个BeanPostProcessor是跟ApplicationListener有关

    他是将单例的ApplicationListener给添加到ApplicationContext中

    再后面就是往BeanFactory里面添加一些跟配置属性相关的单例对象,如果有哪里用到,就可以从BeanFactory中获取到了

    prepareBeanFactory就完了

    正如方法名字的含义一样,就是对BeanFactory做一些配置相关的东西

    比如添加一些BeanPostProcessor,注册一些PropertyEditor

    为Bean的生成做准备操作

    最后画张图来总结一下这个方法的作用

    此时BeanFactory状态就是这样的

    postProcessBeanFactory

    这个方法是一个模板方法

    本身是空实现,是交给子类来扩展,子类可以根据不同的特性再对BeanFactory进行一些准备工作

    比如我们用的这个Web实现就重写了这个方法,对BeanFactory设置一些Web相关的配置

    AnnotationConfigServletWebServerApplicationContext#postProcessBeanFactory
    AnnotationConfigServletWebServerApplicationContext#postProcessBeanFactory

    首先调用父类ServletWebServerApplicationContext的postProcessBeanFactory

    ServletWebServerApplicationContext#postProcessBeanFactory
    ServletWebServerApplicationContext#postProcessBeanFactory

    前两行代码跟之前说的一样,也是添加Aware接口的回调对应的BeanPostProcessor,只不过这个Aware是跟Servlet相关的东西

    接下来调用registerWebApplicationScopes方法,最终会调到下面这个方法

    WebApplicationContextUtils#registerWebApplicationScopes

    这个方法干了两件事

    第一件事就是注册一下Bean在Web环境下的作用域request、session,八股文中的东西

    第二个就是注册一些依赖注入时Bean类型和对应的对象,这在日常开发中还是有用的

    比如可以直接注入一个ServletRequest

    @Resource
    private ServletRequest servletRequest;
    

    所以,父类的实现主要还是对BeanFactory进行一些配置,只不过配置的主要是跟Web环境相关的东西

    现在来看看AnnotationConfigServletWebServerApplicationContext自身的实现

    核心代码就是这两行

    this.scanner.scan(this.basePackages);

    this.reader.register(ClassUtils.toClassArray(this.annotatedClasses));

    scanner和reader就是下面这两个玩意

    也就是说,如果这些配置都不是空的话,那么此时就会扫描对应的包的下Bean,生成对应的BeanDenifition,再注册到DefaultListableBeanFactory

    至于为什么会存到DefaultListableBeanFactory中,可以看看之前的文章

    此时BeanFactory大概是这么一个状态

    除此之外,还有一个贼重要的事

    AnnotationConfigServletWebServerApplicationContext这个ApplicationContext创建时会去创建AnnotatedBeanDefinitionReader

    而AnnotatedBeanDefinitionReader的构造方法最终会调用这么一行代码

    AnnotationConfigUtils#registerAnnotationConfigProcessors(BeanDefinitionRegistry registry)

    这个方法非常重要,他会去注册一些BeanDefinition到BeanFactory中,这里我称为Spring内部的Bean

    这里我说几个常见和重要的

    • ConfigurationClassPostProcessor:这个是用来处理配置类的,非常重要,记住这个类,后面有大用
    • AutowiredAnnotationBeanPostProcessor:处理@Autowired、@Value注解
    • CommonAnnotationBeanPostProcessor:处理@Resource、@PostConstruct等注解

    所以除了扫描出来的一些Bean对应的BeanDefinition,还有一些Spring内部的Bean会注册到BeanFactory中

    此时BeanFactory的状态就如下图所示

    不过,在SpringBoot默认情况下,不会指定包和配置类,也就不会扫描文件,生成BeanDefinition

    但是内部创建的BeanDefinition依然存在,并且在ApplicationContext创建的时候就注册到BeanFactory中了

    所以总结来说,postProcessBeanFactory这个方法是交给子类对BeanFactory做一些准备操作,并且可能会扫描Bean

    invokeBeanFactoryPostProcessors

    从这个方法的名字可以看出,是调用BeanFactoryPostProcessor,这个步骤非常重要,而且过程有点绕

    前置知识:BeanFactoryPostProcessor及其子接口

    BeanFactoryPostProcessor是一个接口,有一个方法,方法参数就是BeanFactory

    通过这个方法就可以拿到BeanFactory,然后对BeanFactory做一些自己的调整

    比如说,你想关闭循环依赖,你就可以实现这个接口,然后进行调整

    他还有一个子接口BeanDefinitionRegistryPostProcessor

    这个接口是对BeanDefinitionRegistry进行调整,BeanDefinitionRegistry就是存BeanDefinition的地方,真实的实现就是DefaultListableBeanFactory

    所以BeanDefinitionRegistryPostProcessor的作用就是往BeanDefinitionRegistry(DefaultListableBeanFactory)中添加BeanDefinition的

    再看invokeBeanFactoryPostProcessors

    有了这两个前置知识之后,我们来看看invokeBeanFactoryPostProcessors方法的实现

    这个方法最终会调用下面方法来真正的处理

    PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors;

    这个方法比较长,大致分为两件事

    • 调用所有的BeanDefinitionRegistryPostProcessor,解析配置类,注册BeanDefinition到DefaultListableBeanFactory中
    • 从BeanFactory中获取所有的BeanFactoryPostProcessor进行调用,完成对BeanFactory一些其它的扩展
    调用BeanDefinitionRegistryPostProcessor

    首先第一步,先从BeanFactory中获取到所有的BeanDefinitionRegistryPostProcessor对象,调用它的postProcessBeanDefinitionRegistry方法

    还记得上一节在说注册Spring内部的Bean时特地强调的一个类ConfigurationClassPostProcessor不?

    他就实现了BeanDefinitionRegistryPostProcessor接口

    所以此时获取到的就是ConfigurationClassPostProcessor

    获取ConfigurationClassPostProcessor的时候会走Bean的生命周期,也就是会回调前面添加的BeansPostProcessor,但是也没几个

    之后会调用他的postProcessBeanDefinitionRegistry方法,来处理此时BeanFactory中的配置类

    配置类从哪来,前面一直没提到过

    但是看一下ApplicationContext是如何使用的就知道了

    比如说,下面这个demo

    在创建一个ApplicationContext之后,在注册一个Bean之后再refresh

    此时这个注册的Bean就是配置类。

    如果你不注册,那是真没有配置类,此时也就没什么意义了。

    所以,ApplicationContext一定会有一个配置类,不然没有意义。

    在SpringBoot条件下,SpringBoot在启动时就会将启动引导类当做配置类给扔到BeanFactory中。

    所以ConfigurationClassPostProcessor最开始处理的时候,就是处理启动引导类

    我们可以在ConfigurationClassPostProcessor方法实现上打个断点验证一下

    在处理之前可以看见,除了几个spring内部的BeanDefinition之外,还有一个myApplication,就是我的启动引导类

    处理的时候它会解析启动引导类的注解,进行自动装配,扫描你写的代码的操作,之后生成BeanDefinition

    当处理完成之后我们再看看,DefaultListableBeanFactory有了非常多的BeanDefinition了

    所以到第一步就完成了,此时BeanFactory就加载了很多Bean

    接下来,由于又新注册了很多BeanDefinition,而这些里面就有可能有BeanDefinitionRegistryPostProcessor接口的实现

    所以之后会重复从BeanFactory中获取BeanDefinitionRegistryPostProcessor,调用postProcessBeanDefinitionRegistry

    一直会循环下去,直到所有的BeanDefinitionRegistryPostProcessor都被调用为止

    由于BeanDefinitionRegistryPostProcessor继承BeanFactoryPostProcessor

    所以之后也会调用BeanDefinitionRegistryPostProcessor的postProcessBeanFactory方法

    调用BeanFactoryPostProcessor

    当调完所有的BeanDefinitionRegistryPostProcessor实现方法

    之后就会从BeanFactory获取所有的BeanFactoryPostProcessor(除了BeanDefinitionRegistryPostProcessor实现之外),调用postProcessBeanFactory方法

    此时就可以通过BeanFactoryPostProcessor再次对BeanFactory进制扩展

    总的来说,这一步骤的核心作用就是完成对BeanFactory自定义扩展,但是由于BeanFactoryPostProcessor都是Bean,所以要第一步先加载Bean,之后才能通过BeanFactoryPostProcessor来扩展

    一张图来总结上面主要干的事

    这里简化了一些前面提到东西

    registerBeanPostProcessors

    上面一个步骤已经完成了Bean的扫描和对BeanFactory的扩展

    这一节通过方法名就可以看出,是跟BeanPostProcessor相关

    不过在这个方法执行之前,我们先来看看此时BeanFactory中已经有了哪些BeanPostProcessor

    此时只有4个,前3个前面都提到过,但是像我们熟知的处理@Autowired、@Resource注解的BeanPostProcessor都不在里面

    所以这里就有一个非常重要的小细节

    在当前这个步骤执行之前如果从BeanFactory中获取Bean的话,虽然会走Bean生命周期的整个过程,但是@Autowired、@Resource注解都不会生效,因为此时BeanFactory中还没有处理这些注解的BeanPostProcessor(CommonAnnotationBeanPostProcessor等)

    什么意思呢,举个例子

    比如上面一节,在当前步骤执行之前会从BeanFactory中获取BeanFactoryPostProcessor

    假设现在你实现了BeanFactoryPostProcessor,想注入一个ApplicationContext对象

    此时是注入不成功的,@Resource注解不会生效,就是这个意思。

    这时只能通过ApplicationContextAware方式获取,因为有对应的BeanPostProcessor(ApplicationContextAwareProcessor)

    接下来我们再来看看registerBeanPostProcessors实现

    最终也是调用下面的方法

    PostProcessorRegistrationDelegate#registerBeanPostProcessors

    这个过程就没上面那个步骤复杂了

    其实就是从BeanFactory中获取到所有的BeanPostProcessor,然后添加到BeanFactory中

    不过值得注意的是,BeanPostProcessor创建会有优先级,优先级高的会先被创建和添加到BeanFactory中

    到这一步其实BeanFactory就算是准备完成了,基本上跟创建Bean相关的前置操作几乎都完成了

    最后再来张图总结一下这个方法干的事

    initMessageSource

    这个方法是处理国际化相关的操作

    这个操作比较简单,就是从BeanFactory中看看有没有Bean名称为messageSource的Bean

    有的话就使用这个MessageSource,没有的话就用默认的

    不过SpringBoot项目下会自动装配一个MessageSource,所以此时容器中是有的

    initApplicationEventMulticaster

    这个方法跟上面的差不多,也是从BeanFactory找有没有ApplicationEventMulticaster

    有就用容器中的,没有就自己创建一个

    ApplicationEventMulticaster是真正用来发布事件的,ApplicationEventPublisher最终也是调用他来发布事件

    ApplicationEventMulticaster内部会缓存所有的监听器

    当通过ApplicationEventMulticaster发布事件的时候,会去找到所有的监听器,然后调用

    onRefresh

    onRefresh也是一个模板方法,本身也是空实现

    子类重写这个方法,会去创建一个Web服务器

    registerListeners

    这个方法其实也比较简单,就是将监听器给添加到ApplicationEventMulticaster中

    finishBeanFactoryInitialization

    这个方法首先又是老套路,就是判断容器中有没有ConversionService

    ConversionService也是用来做类型转换的,跟前面提到的PropertyEditor作用差不多

    如果有,就把ConversionService设置到BeanFactory中

    到这一步,BeanFactory才算真的准备完成。。。

    之后其实干的事就不太重要了

    但是最后一行比较重要

    beanFactory.preInstantiateSingletons();

    从方法的命名就可以看出,实例化所有的单例对象

    因为对于BeanFactory的一些配置在前面都完成了,所以这里就可以来实例化所有的单例对象了

    这个方法会做两件事

    第一件事就是实例化所有的非懒加载的单例Bean

    实际上就是通过getBean方法来的,因为获取Bean,不存在的时候就会创,会走Bean的生命周期

    第二件事就是一旦单例Bean实现了SmartInitializingSingleton接口,就会调用SmartInitializingSingleton的afterSingletonsInstantiated方法

    这个其实也算是Bean生命周期的一部分。

    finishRefresh

    这个方法是整个Spring容器刷新的最后一个方法

    这个方法就是收尾的操作

    清理一下缓存操作

    之后就是初始化LifecycleProcessor

    都是一样的套路,优先用BeanFactory中的

    后面就会调用LifecycleProcessor#onRefresh方法

    这个方法的作用就是,如果你的Bean实现了SmartLifecycle的接口,会调start的方法

    随后就发布一个ContextRefreshedEvent事件,表明容器已经刷新完成了

    在Web环境底下,这个finishRefresh方法被重写了

    主要是多干了一件事,那就是启动Web服务器

    并且会发布了一个ServletWebServerInitializedEvent事件

    这个事件在SpringBoot中用的不多

    但是在SpringCloud中却非常重要

    在SpringCloud环境底下会有一个类监听这个事件

    一旦监听到这个事件,SpringCloud就会将当前的服务的信息自动注册到注册中心上

    这就是服务自动注册的原理

    总结

    这里再来简单回顾一下Spring启动大致的几个过程

    最开始的准备操作,这部分就是准备一些配置属性相关的

    之后连续好几个方法都是准备BeanFactory的,我把上面那张图拿过来

    整个准备BeanFactory过程大致如下:

    • 先配置BeanFactory
    • 通过ConfigurationClassPostProcessor加载Bean到BeanFactory中
    • 从上一步加载的Bean中获取BeanFactoryPostProcessor,完成对BeanFactory做自定义处理
    • 从上一步加载的Bean中获取BeanPostProcessor,添加到BeanFactory中

    当这些步骤完成之后,BeanFactory跟Bean创建相关的配置几乎算是配置完成了

    之后其实就是一些ApplicationContext内部的一些组价的初始化,比如MessageSource、ApplicationEventMulticaster等等

    优先从BeanFactory中获取,没有再用默认的

    到这ApplicationContext也算配置完成了,之后就可以实例化单例非懒加载的Bean了

    再后面就是一些扫尾的操作,发布一个ContextRefreshedEvent事件,表明容器已经刷新完成了

    这时Spring就就算是真正启动完成了。

    最后,如果本篇文章对你所有帮助,欢迎转发、点赞、收藏、在看,非常感谢。

    最后的最后,再来留个坑,有机会再来扒一扒SpringBoot在启动时都做了哪些事

    至于啥时候填,那就等一个有缘人吧。。

    往期热门文章推荐

    如何去阅读源码,我总结了18条心法

    如何写出漂亮代码,我总结了45个小技巧

    1.5万字+30张图盘点索引常见的11个知识点

    三万字盘点Spring/Boot的那些常用扩展点

    三万字盘点Spring 9大核心基础功能

    两万字盘点那些被玩烂了的设计模式

    扫码或者搜索关注公众号 三友的java日记 ,及时干货不错过,公众号致力于通过画图加上通俗易懂的语言讲解技术,让技术更加容易学习,回复 面试 即可获得一套面试真题。

  • 相关阅读:
    [免费专栏] Android安全之Android Fragment注入
    前端跨界面之间的通信解决方案
    python数据分析及可视化(十三)pyecharts可视化(简介、特性、全局配置项、图形的绘制、多图布局)
    疏水18碳磷脂磷脂-聚乙二醇-羧基,DSPE-PEG-Acid,CAS:1403744-37-5
    Excel管理Simulink SWC中的标定量与观测量之观测量
    vue--vuex 状态管理模式
    WebRTC系列--sdp协商中的answer编解码协商过程
    QT学习日记7——QMainWindow
    flutter 阿里云上传文件
    Node.js | 基础完结、综合训练 —— 路由应用实战教程
  • 原文地址:https://www.cnblogs.com/zzyang/p/17695977.html