spring循环依赖上篇- spring整体启动流程

  很久没有写博客了, 感觉没有学到让我自己眼前一亮的东西,所以还在摸索当中; 不过最近在复习spring相关的内容, 特别是循环依赖这块, 查询了很多的资料, 比较有收获, 就分享一下吧!

  分为上下两篇博客,  第一篇是复习一下spring的整体流程, 第二篇说一下循环依赖

  提前须知: 最好自己看过spring源码, 了解bean的生命周期

1. 问答环节

  先问几个问题,

  问题一: 我们抛开框架层面的理解, 你觉得IOC容器本质上是一个啥?

  回答: ioc的本质上就是new一个大的普普通通的对象, 这个对象中有非常多成员变量,  有字符串类型, 有的是map类型, 有的是list类型, 还有的是数组类型,  set类型, 等等

  问题二: 那么IOC容器的启动的本质上又是什么呢?

  回答: 启动的本质就是我们根据一个BeanFactory的class文件,  去new一个实例出来,  然后完成初始化操作, 也就是给这个实例中的所有成员变量赋值

  问题三: 那么Bean的生命周期本质上又是一个啥?

  回答: Bean的生命周期本质上,  就是先从xml文件或者注解中获取到很多类的全类名和属性信息,  封装成对象,  然后存到IOC容器的一个map成员变量中; 然后下一步遍历这个map, 将这个map中的对象的类信息提取出来,  使用反射,  实例化成另外的对象(也就是这里判断出来需要单例还是多例Bean对象),  然后把新创建的对象再放到IOC容器的另外一个map中;

  前一个map就是存放的BeanDefinition,  后一个map就是存放单实例Bean对象

  问题四: 那么你觉得BeanPostProcessor本质上又是一个啥?

  回答: BeanPostProcessor分为两种, 一种是BeanFactoryProstProcessor, 另外一种是BeanPostProcessor;

  前者是为了给BeanFactory实例填充成员变量之后,  可以对某些成员变量做些自定义的修改, 比如对存放BeanDefiniton的那个map进行遍历, 拿到想要的BeanDefinition对象,  把里面的属性清空掉,  以满足我们扩展spring框架然后天天改bug的需求;   后者BeanPostProcessor是在实例化Bean对象, 然后设置属性值之后,  我们可以对这个Bean实例的所有成员变量做些鬼畜的处理;

  下图所示, 咱亲手画的(╯-╰)/, 请你务必等下也要动手画一张

 

2.Bean的生命周期

  这个是很经典的东西了,  大概把生命周期分为三个部分吧, 前提是创建一个BeanFactory容器肯定不用说, 容器肯定要首先创建, 不然连家都没有, 还初始化尼玛的bean对象啊╮(╯_╰)╭

  然后我们首先加载类的基本信息,  然后使用反射实例化Bean,   最后初始化Bean对象, 给对象的成员变量赋值

  是不是跟类加载的步骤很像啊, 只不过jvm加载类的时候是一气呵成的, 而在spring中是每个步骤都分开的, 在每个步骤前后都会经历很多复杂的初始化和增强操作

  2.1 加载

  这个加载类的定义信息,  这个定义信息在哪里?肯定在xml配置文件中或者使用注解标识了呀!

  spring容器在启动的时候, 通过BeanDefinitionReader去读取配置文件(这里可不是只有xml文件啊,还有可能是properties,yml等),这里具体的需要说一下, 比如我们有一个下面这样的xml文件(稍等, 我去网上复制一下),  那么是怎么加载到注解@Controller, @Service等那些类的呢

 

  先说结论:  用脚想也能知道肯定是在BeanDefinitionReader去读取这个配置文件进行解析的时候, 当读取到了<component-scan>标签, 然后根据这个标签配置的类路径进行扫描该目录下的所有class类, 看看有没有@Controller,@Component,@Service等注解, 有的话, 就把这些类给的信息收集起来变成BeanDefinition对象, 丢到IOC容器的某个角落里的Map中保存起来; 

  当解析到<bean>标签的时候, 也会最终解析为BeanDefinition对象,  然后也保存在上面的这个Map中, 这样就在项目启动的时候,  收集了注解标注的bean和xml配置文件配置的定义信息了

  结论说完, 下面看一下大概的源码流程,  不想看的小伙伴可以直接跳过( ̄▽ ̄)ノ

  2.1.1. 基于xml的ioc容器入口

 

  2.1.2.ioc容器的主干脉络

 

  

  2.1.3.ioc容器类图

  ioc容器的实际类型是DefaultListableBeanFactory, 希望你能记住这个类名, 看一下这种类的类图, 可以看到这个DefaultListableBeanFactory的功能是十分全面的

 

 

  实例化BeanDefinitionReader, 然后去解析文件

 

 

  到了这里赶紧去喝一口水, 这个loadBeanDefinitions方法中间有很多跳转就不看了, 我们只看最终到的解析类,  截图也可以少一点๑乛◡乛๑

 

 

  2.1.4 注解类的加载流程

  熟悉spring的扩展机制的都知道, xml配置文件最上面是有很多url一样的东西, 这是为了注册处理器然后去解析不同的标签的, 有兴趣的可以看看这篇博客

  反正最后就是由一个ContextNamespaceHandler来解析这个component-scan标签

 

  

  这里可以看到是去加载applicationContext.xml的中所有命名空间的处理器, 处理器中对每个命名空间下的每一种标签都注册了一种解析器, 后续解析具体标签的时候, 就是使用该解析器

 

 

 

  这里就是加载spring.handlers加载所有命名空间的对应的处理器

 

 

 

  每一个处理器中又给每一种标签对应一个解析器, 我们的component-scan标签对应的是ComponentScanBeanDefinitionParser解析器

 

  这里是最终会调用ComponentScanBeanDefinitionParser的parse方法真正的去解析<component-scan>标签的属性值了

 

 

  很明显解析的这个component标签的处理类是ContextNamespaceHandler, 这个处理器中真正去解析component-scan标签的是ComponentScanBeanDefinitionParser, 这个类的parse方法

  就是去扫描配置的包路径, 然后加载那些注解类, 变成BeanDefinition对象的, 有兴趣的继续往底下看吧,

 

  继续点进去都Scan方法内部就能看到去遍历扫描找到对应的类, 然后收集这些类的定义信息BeanDefinition

 

  收集了那些信息之后, 然后再注册到IOC容器的某个Map中保存起来

 

 

 

 

  原来IOC容器中最终的存放BeanDefinition的地方叫做beanDefinitionMap啊

 

 

   上面说的是解析注解类的定义信息,  解析完了之后,  也是根据命名空间对应的解析器来解析xml文件中<bean>中配置的信息, 然后变成BeanDefinition信息,  这个就不细看了, 无非就是解析xml标签中各个属性, 然后给BeanDefinition对象赋值

  我们可以发现不管是注解类配置的Bean,  还是配置文件中配置的Bean, 在加载的过程都会被加载成统一的BeanDefinition对象,  这个BeanDefinition对象屏蔽了配置文件和注解的差异性,  使得在后面处理的时候, 不需要花费额外的操作

 

  2.2 实例化

  收集所有BeanDefinition, 保存到Map之后, 我们只需要遍历这个Map, 对里面的一个一个BeanDefinition使用反射, 进行实例化就行了, 这个还是很容易的,我们一起看看源码

  入口还是在这个refresh方法这里, 找到调用finishBeanFactoryInitialization(beanFactory)方法, 点进去找到beanFactory.preInstantiateSingletons(), 继续往下看之前,  先看一眼大概的流程:

getBean->doGetBean->createBean->doCreateBean->createBeanInstance->instantiateBean->instantiate,  根据这个名称都应该知道在干啥了吧, 在最终的instantiate方法中(这里只针对于构造器创建实例bean),如果这个bean是继承父类, 并且有重写父类方法, 会使用cglib字节码的技术创建bean对象,  否则就用jdk自带的反射的方式创建对象

 

   2.2.1 getBean

 

  2.2.2.doGetBean

 

 

   2.2.3. createBean

 

  2.2.4.doCreateBean

 

  2.2.5.createBeanInstance

  这个方法就是进行各种校验, 看看使用哪种创建对象的方式, 因为我们可以xml文件bean的标签中使用factory-mtehod等工厂方法去创建的嘛! 

  现在嘛, 我们肯定是用最简单最朴素的方式去创建, 直接获取构造器, 然后根据构造器去创建

 

  注意: Cglib不止能用来做动态代理,  也可以用于创建对象啊,  是基于字节码框架 ASM 实现,所以可以直接通过 ASM 操作指令码来创建对象

 

  

  3.3.初始化

  初始化方法其实就是给上一步反射生成的bean实例, 设置我们自己定义的属性值, 入口是doCreateBean方法

  populateBean这个方法很重要,  但是我就是不点进去看( ̄o ̄) . z Z,

  里面大概的逻辑说一下, 就是xml配置文件<bean>标签下可能有<property name="xx", value="xxx"></property>这样的值,  取出value赋值到bean实例里面去; 与此同时, 如果这个bean有类似于@Autowired等标签的, 也会去依赖注入对应的类实例,  依赖注入其实还是调用getBean方法创建对应的依赖类, 有点像递归,  然后你就又可以从本篇博客最上面开始往下看了,( ̄▽ ̄)ノ

  然后说说initializeBean方法之前,  首先说一下什么是系统属性? 比如在我们写业务代码的时候, 开发一个UserService类, 如果想用BeanFactory, ApplicationContext, Environment等系统对象怎么办, 有没有什么好的办法呀?

  spring中提供了一种扩展机制,  只要实现了Aware接口的时候, 在执行initializeBean方法的时候,  就会填充这些系统属性给我们的Bean实例,  例如:BeanFactoryAware, ApplicationContextAware, EnvironmentAware等接口, 下图所示

 

  其中在执行初始化方法之前, 会判断当前Bean是否实现了InitializingBean接口, 如果实现了的话,  就执行afterPropertiesSet方法,  这个方法也可以用于初始化

 

  到这里其实就已经把spring中bean的生命周期说完了, 代码流程也大概看了一下, 看的不是很细, 其实很多地方都可以用很长的篇幅进行说明的, 考虑到我只想使用一篇博客写完整个流程,  就只能很简略的看了一下, 有兴趣的小伙伴可以自己深入看一下, 嘿嘿( ̄▽ ̄)ノ

posted @ 2022-06-18 17:52  java小新人  阅读(197)  评论(0编辑  收藏  举报