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去读取这个配置文件进行解析的时候, 当读取到了标签, 然后根据这个标签配置的类路径进行扫描该目录下的所有class类, 看看有没有@Controller,@Component,@Service等注解, 有的话, 就把这些类给的信息收集起来变成BeanDefinition对象, 丢到IOC容器的某个角落里的Map中保存起来; 

  当解析到标签的时候, 也会最终解析为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标签的处理类是ContextNamespaceHandler, 这个处理器中真正去解析component-scan标签的是ComponentScanBeanDefinitionParser, 这个类的parse方法

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

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

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

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

   上面说的是解析注解类的定义信息,  解析完了之后,  也是根据命名空间对应的解析器来解析xml文件中中配置的信息, 然后变成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配置文件标签下可能有这样的值,  取出value赋值到bean实例里面去; 与此同时, 如果这个bean有类似于@Autowired等标签的, 也会去依赖注入对应的类实例,  依赖注入其实还是调用getBean方法创建对应的依赖类, 有点像递归,  然后你就又可以从本篇博客最上面开始往下看了,( ̄▽ ̄)ノ

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

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

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

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