• 轻松读懂spring之 IOC的主干流程


    前言

    最近写的几篇spring系列的文章,收到了很多读者的好评,有些读者希望我再多写几篇这方面的文章。甚至还有读者私信给我,向我请教看spring源码的方法,为此我打算写一个spring源码解读的系列,回馈给一直支持我的粉丝们。

    不知道你有没有这些经历:

    • 想看spring的源码无从下手

    • spring源码太多,看着看着就跟丢了

    • 不知道哪些是主要的,哪些是次要的

    • 前几天还记得,今天就忘了

    spring源码很复杂,说实话这类文章不好写,想把它讲清楚很难,写着写着篇幅会很长,读者不一定有耐心看下去,而且看完容易忘记。

    我打算用图文相结合的方式,去除糟粕,只解读一些精华部分,给读者们在阅读源码时一个清晰的思路,不至于迷路。另外最关键的是,看完之后可以记住很多关键流程。

    spring的庞大体系中,IOC(控制反转)贯穿始终,其作用不言而喻。我们就先从IOC开始,介绍它的主干流程,给有需要的朋友一些指引。

    入口

    spring容器的顶层接口是:BeanFactory,但我们使用更多的是它的子接口:ApplicationContext

    通常情况下,如果我们想要手动初始化通过xml文件配置的spring容器时,代码是这样的:

    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");User user = (User)applicationContext.getBean("name");

    如果想要手动初始化通过配置类配置的spring容器时,代码是这样的:

    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Config.class);User user = (User)applicationContext.getBean("name");

    这两个类应该是最常见的入口了,它们却殊途同归,最终都会调用refresh方法,该方法才是spring容器初始化的真正入口。

    f16ed4118d1a4217882c8c1893f44664.pnga4e11cfedafa4faa992104fc11792d02.png  

    顺便提一下,其实调用refresh方法的类并非只有这两个,我们用一张图整体认识一下:

    56d21a54714d4617a7aa74cdb76a23b7.png 

    虽说调用refresh方法的类有这么多,但我决定用ClassPathXmlApplicationContext类作为列子给大家讲解,因为它足够经典,而且难度相对来说要小一些。

    再次重申一下,由于spring源码代码量巨大,即使我能一次性讲完,恐怕你也没那么多耐心看下去。所以我会采用你好,我也好的方式,忽略一些细枝末节,只抓重点。如果有对某些细节比较感兴趣的同学,欢迎关注我后续的文章,将会做详细的讲解。

    refresh方法

    refresh方法是spring ioc的真正入口,它负责初始化spring容器。

    既然这个方法的作用是初始化spring容器,那方法名为啥不叫init

    答案很简单,因为它不只被调用一次。

    springbootSpringAppication类中的run方法会调用refreshContext方法,该方法会调用一次refresh方法。

    springcloudBootstrapApplicationListener类中的onApplicationEvent方法会调用SpringAppication类中的run方法。也会调用一次refresh方法。

    这是springboot项目中如果引入了springcloud,则refresh方法会被调用两次的原因。

    springmvcFrameworkServlet类中的initWebApplicationContext方法会调用configureAndRefreshWebApplicationContext方法,该方法会调用一次refresh方法,不过会提前判断容器是否激活。

    所以这里的refresh表示重新构建的意思。

    好了,废话不多说。下面重点看看refresh的关键步骤:

    ad6d904917aa4d9f8bf90122d977a100.png 

    其实上图中一眼看过去好像有很多方法,但是真正的核心的方法不多,我主要讲其中最重要的:

    • obtainFreshBeanFactory

    • invokeBeanFactoryPostProcessors

    • registerBeanPostProcessors

    • finishBeanFactoryInitialization

    解析xml配置文件

    obtainFreshBeanFactory方法会解析xml的bean配置,生成BeanDefinition对象,并且注册到spring容器中(说白了就是很多map集合中)。

    经过几层调用(细节不说,很简单),会调到AbstractBeanDefinitionReader类的loadBeanDefinitions方法:

    77f5e548f84c4153a1bd2ea28c364b37.png 

    该方法会循环locations(applicationContext.xml文件路径),调用另外一个loadBeanDefinitions方法,一个文件一个文件解析。

    经过一些列的骚操作,会将location转换成inputSource和resource,然后再转换成Document对象,方面解析。

    8e49f0cff52547a4a24ebc9ad89d1ab4.png 

    在解析xml文件时,需要判断是默认标签,还是自定义标签,处理逻辑不一样:

    246f5ce0047d49d99d4409b1bad93f7e.png 

    spring的默认标签只有4种:

    对应的处理方法是:

    1d012c34bf3649b1bde85a46523d8bd6.png 

    注意常见的:等都是自定义标签。

    从上图中处理标签的processBeanDefinition方法开始,经过一系列调用,最终会调到DefaultBeanDefinitionDocumentReader类的processBeanDefinition方法。

    5309840a06724ff093ce20a10bfedec0.png 

    这个方法包含了关键步骤:解析元素生成BeanDefinition 和 注册BeanDefinition。

    自定义属性的内容有趣,但是不这里不会讲,现在用得不多。

    生成BeanDefinition

    下面重点看看BeanDefinition是如何生成的。

    上面的方法会调用BeanDefinitionParserDelegate类的parseBeanDefinitionElement方法:

    97b03c8cf11b4d788540f4a041324701.png 

    一个标签会对应一个BeanDefinition对象。

    该方法又会调用同名的重载方法:processBeanDefinition,真正创建BeanDefinition对象,并且解析一系列参数填充到对象中:

    0d9837d5412844cf9940bc58d0ff52ac.png 

    其实真正创建BeanDefinition的逻辑是非常简单的,直接new了一个对象:

    e34dc21ee64e4c18a01cefd6710ed4a4.png 

    真正复杂的地方是在前面的各种属性的解析和赋值上。

    注册BeanDefinition

    上面通过解析xml文件生成了很多BeanDefinition对象,下面就需要把BeanDefinition对象注册到spring容器中,这样spring容器才能初始化bean。

    BeanDefinitionReaderUtils类的registerBeanDefinition方法很简单,只有两个流程:

    a1c21419284247f5ae848f1be3608db5.png 

    先看看DefaultListableBeanFactory类的registerBeanDefinition方法是如何注册beanName的:

    ff10dceb2485440ebf20a59138e3fd45.png 

    接下来看看SimpleAliasRegistry类的registerAlias方法是如何注册alias别名的:

    52f5f426a5404788b85953d12e44fad0.png 

    这样就能通过多个不同的alias找到同一个name,再通过name就能找到BeanDefinition

    修改BeanDefinition

    上面BeanDefinition对象已经注册到spring容器当中了,接下来,如果想要修改已经注册的BeanDefinition对象该怎么办呢?

    refresh方法中通过invokeBeanFactoryPostProcessors方法修改BeanDefinition对象。

    经过一系列的调用,最终会到PostProcessorRegistrationDelegate类的invokeBeanFactoryPostProcessors方法:

    b0b92c39a2c846da9186def40f6967ff.png 

    流程看起来很长,其实逻辑比较简单,主要是在处理BeanDefinitionRegistryPostProcessorBeanFactoryPostProcessor

    BeanDefinitionRegistryPostProcessor本身是一种特殊的BeanFactoryPostProcessor,它也会执行BeanFactoryPostProcessor的逻辑,只是加了一个额外的方法。

    7270e04e4bda4efa9a3009b946147c08.png 

    ConfigurationClassPostProcessor可能是最重要的BeanDefinitionRegistryPostProcessor,它负责处理@Configuration注解。

    注册BeanPostProcessor

    处理完前面的逻辑,refresh方法接着会调用registerBeanPostProcessors注册BeanPostProcessor,它的功能非常强大,后面的文章会详细讲解。

    经过一系列的调用,最终会到PostProcessorRegistrationDelegate类的registerBeanPostProcessors方法:

    0e9cb73a913841108cebdb3dcf2ad1e0.png 

    注意,这一步只是注册BeanPostProcessor,真正的使用在后面。

    总结

    前面主要介绍了:

    1. spring容器初始化的入口

    2. refresh方法的主要流程

    3. 解析xml配置文件

    4. 生成BeanDefinition

    5. 注册BeanDefinition

    6. 修改BeanDefinition

    7. 注册BeanPostProcessor

    以上内容只是spring容器初始化的前期准备工作,预告一下,真正的好戏在后面的:实例化Bean依赖注入初始化BeanBeanPostProcessor调用等。

    最后说一句(求关注,别白嫖我)

    如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下,您的支持是我坚持写作最大的动力。

     

  • 相关阅读:
    边界外推和边界处理--cv::copyMakeBorder()和cv::borderInterpolate()
    聊聊计算机中的寄存器
    无人机测试用例
    python循环的花样玩法(一)
    java学习——异常处理机制
    arraybuffer 转json
    论文阅读——Align before Fuse
    NSIS:禁止选择安装路径和编辑安装目录
    Markdown基础教程
    SpringBoot详解配置文件
  • 原文地址:https://blog.csdn.net/m0_72088858/article/details/126757250