spring循环引用
spring循环引用
Ycspring源码(一)spring循环引用
spring在默认单例的情况下是支持循环引用的
上图这两个类相互引用了对方(循环依赖)spring在单例、非构造方法注入的情况下是允许这样的循环依赖 上面的代码可以正常输出 从容器中可以获取到Xbean Ybean
Spring的循环依赖可以关闭
1、spring提供的APi
2、修改源码
循环依赖无非就是属性注入
spring的属性注入属于Spring bean的生命周期的一部分
两个相关概念:
1、spring bean——受spring容器管理的对象,可能经过了完整的spring生命周期(为什么是可能?难道还有bean是没有经过bean生命周期的?答案是有的),最终存在spring容器当中;一个bean一定是个对象。
2、对象——任何符合java语法规则实例化出来的对象,一个对象并不一定是spring bean 所谓bean的生命周期就是磁盘上的类通过扫描,然后实例化,跟着初始化,继而放到容器当中的过程。
spring bean的生命周期经历步骤
1:实例化一个ApplicationContext的对象;
2:调用bean工厂后置处理器完成扫描;
3:循环解析扫描出来的类信息;
4:实例化一个BeanDefinition对象来存储解析出来的信息;
5:把实例化好的beanDefinition对象put到beanDefinitionMap当中缓存起来,以便后面实例化bean;
6:再次调用bean工厂后置处理器;
7:当然spring还会干很多事情,比如国际化,比如注册BeanPostProcessor等等,如果我们只关心如何实例化一个bean的话那么这一步就是spring调用finishBeanFactoryInitialization方法来实例化单例的bean,实例化之前spring要做验证,需要遍历所有扫描出来的类,依次判断这个bean是否Lazy,是否prototype,是否abstract等等;
8:如果验证完成spring在实例化一个bean之前需要推断构造方法,因为spring实例化对象是通过构造方法反射,故而需要知道用哪个构造方法;
9:推断完构造方法之后spring调用构造方法反射实例化一个对象;注意这里说的是对象、对象、对象;这个时候对象已经实例化出来了,但是并不是一个完整的bean,最简单的体现是这个时候实例化出来的对象属性是没有注入,所以不是一个完整的bean;
10:spring处理合并后的beanDefinition(合并?是spring当中非常重要的一块内容,后面会分析);
11:判断是否支持循环依赖,如果支持则提前把一个工厂存入singletonFactories——map;
12:判断是否需要完成属性注入
13:如果需要完成属性注入,则开始注入属性
14:判断bean的类型回调Aware接口
15:调用生命周期回调方法
16:如果需要代理则完成代理
17:put到单例池——bean完成——存在spring容器当中
spring大概在什么时候实例化bean的
spring在AbstractApplicationContext#finishBeanFactoryInitialization方法中完成了bean的实例化。这点需要记住
spring bean实例化的过程理论:
假设磁盘上有X、Y类,spring启动时会扫描到X、Y被加了注解(@Autowired)然后会把X、Y的class信息封装成为一个beanDefinition对象,这个beanDefinition对象包含了当前类的信息,比如当前类是不是抽象的、当前这个类的描述、当前这个类的类型、当前这个类是不是懒加载等等这些信息。封装完之后会把beanDefinition对象放到一个Map集合当中,如果有的话执行beanFactoryPostProcessor(扩展)。然后开始验证,如果验证都通过的话调用prelnstantiateSingletons方法(过程相当复杂) 开始实例化当前类,实例化之后成为一个spring bean放到单例池里面。
源码验证:
这段代码用来初始化spring容器,当这段代码执行完之后,XY已经被实例化好了
debug源码找到在哪里实例化好的bean
beanFactory->beanDefinitionMap中还不存在x、y类
扫描具体的实现代码
put到beanDefinitionMap
调用太多了
看源码中在AnnotationConfigApplicationContext类的父类定义了beanFactory
这个beanFactory就是我们常说的spring bean工厂 它是DefaultListableBeanFactory的实例
在DefaultListableBeanFactory中有一个属性叫做 beanDefinitionMap ConcurrentHashMap 1.7、1.8 解决并发
执行完invokeBeanFactoryPostProcessors方法发现beanDefinitionMap中有了x、y
所以nvokeBeanFactoryPostProcessors中完成了扫描把类变成了beanDefinition对象(注意这个时候spring并没有对bean进行实例化)
而是在finishBeanFactoryInitialization实例化单例的bean
BeanFactoryPostProcessors:
bean工厂后置处理器 能够干扰spring当中bean工厂初始化过程?
扩展:可以在初始化前后做一些事情 实现这个接口可以拿到 beanFactory可以对beanFactory做一些改变等
重点调用:finishBeanFactoryInitialization方法中beanFactory.preInstantiateSingletons();
拿到beanNames集合 为了遍历
为什么要有 beanDefinition:
用来描述spring bean的里面包含了spring bean的所有信息(因为普通的类 不能描述 scope lazy等等等等 ) 调用到doGetBean ac.getBean其实底层就是调用这个doGetBean方法
singletonObjects:单例池
创建bean之前的关键代码
singletonsCurrentlyInCreation 正在创建的单例bean的名字集合 放在这个集合当中
关键代码 调用传过来的 ObjectFactory<?> singletonFactory的方法 lambda
再往下执行调到这里
到此bean 还没有被创建
创建对象
依次进入方法内部
这个时候只是实例化出来了对象还不是一个bean
判断支持循环依赖 allowCircularReferences默认等于true
spring处理循环依赖时候涉及的三个Map
执行完这个方法后注入的属性有值了
接下来调用initializeBean这个方法
//执行部分Aware接口
生命周期方法的回调
生命周期回调初始化方法有三种实现方式 可以同时存在一个bean当中
因为在源码中执行时机是不一样的
spring bean的生命周期 调用过程
会按照字母顺序创建(可以更改)
循环依赖!
x属性注入 填充y 调到了doGetBean(“y”) 肯定拿不到
正在创建x的过程当中创建y所以singletonsCurrentlyInCreation有x
X和Y正在被创建的过程当中
执行这个方法之前y还没有别创建 一旦执行完 控制台打印y
这个时候y要注入x所以回去getBean(“x”)
关键代码: 这个时候返回的肯定不能是null如果返回null的话就成了死循环了 又会去走创建x的步骤
(从单例池当中去拿x但是 x也肯定不在单例池当中因为它没有走完bean的生命周期)
getSingleton源码 注意第二个参数写死了true
单例池——一级缓存主要存放单例bean (所谓的容器)
现在去从单例池当中去拿x 拿不到 返回 null
这里判断x是不是正在被创建 true
这里还是拿不到 原因很简单,因为前面根本就没有网这个map三级缓存中放入
这里肯定成立
这个地方可以拿到 但是这个不是x类型 它是Factory
问题: 1、工厂怎么来的?
2、为什么不直接在二级缓存中存一个x,而要存一个工厂?
3、x和工厂怎么联系起来?
4、工厂造出来的x和正在创建的x是同一个吗 ? 是同一个
5、如果不是循环依赖的bean会不会执行三级缓存 ? 答: 不会
回答1:与bean的生命周期有关,下图
回答2:
因为spring如果直接把x存进二级缓存,那么在y注入x的时候,需要的x不是当前存进去的x
解释:存工厂可以产生任何对象,比存一个对象更加丰富 可以对x进行加工
回答三:
先把x传进去,然后增强,返回出来
回答四: 默认是同一个
为什么要添加到三级换存当中 :把它放到三级缓存 (x依赖y,y依赖x,x依赖z,z依赖x的情况下,x就不需要重复从工厂产生了),说白了为了效率
为什么要从二级缓存remove掉:为了gc 资源回收
singletonsCurrentlyInCreation 这个集合80%是为了循环依赖而使用的
beanDefinition A类去掉注解
然后通过ac来获取a 肯定会报错 因为A不在spring容器当中
调用了程序员或者spring当中提供的所有的postProcessors
//拿到spring当中所有的BeanFactoryPostProcessor对象 执行postProcessBeanDefinitionRegistry方法 完成的扫描,扫描之后就变成了beanDefinition
所谓的扫描就是执行spring内部的BeanFactoryPostProcessor的子类当中的postProcessBeanDefinitionRegistry方法完成的扫描
怎么区分是spring内部和程序员自己提供的?
找出所有实现了接口的子类,并且找了两次,代码几乎一摸一样 第一次找spring自己内部的 第二次找程序员写的
spring 扩展点
比如:把class给改了 改成了A,现在就可以拿到A了 而B就拿不到了
写在后面:
我是YC一位普通的从事软件开发行业的工作者,工作了很多年第一次写的一篇技术博客。这也是我学习到的东西,做一个分享吧!再此感谢子路老师,他的源码性的东西讲的非常的深入和细致。我也是听了他的课之后做一个总结,再次感谢!