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的生命周期说完了, 代码流程也大概看了一下, 看的不是很细, 其实很多地方都可以用很长的篇幅进行说明的, 考虑到我只想使用一篇博客写完整个流程, 就只能很简略的看了一下, 有兴趣的小伙伴可以自己深入看一下, 嘿嘿( ̄▽ ̄)ノ
标签:xml,spring,流程,Bean,实例,标签,上篇,BeanDefinition 来源: https://www.cnblogs.com/wyq1995/p/16388885.html