spring的ioc思想中的一个重要实现就是DI:依赖注入。自动为spring所管理的bean进行注入属性,那么如果一个A类中有个属性b,而B类中有个属性a,而这两个类的对象是由spring管理的话,那么就会出现循环依赖的问题。
循环依赖是如何产生的? 我们都知道bean的生命周期中首先是实例化对象,给对象开辟一个内存空间,这个时候属性还没有赋值都是默认值。接着是populateBean阶段(填充属性),当A对象(后面简称A)实例化完成之后,需要对其中的b属性进行填充,这个时候它会去单例池(一级缓存\singletonObject\它是一个Map集合)去寻找有没有对象B(后面简称B),如果没有这时候他就去利用BeanFactory去创建B,B创建也是遵循bean的生命周期,先实例化再populateBean,这个时候B要填充a属性,而A这个时候还没有变成一个成品Bean(经历过初始化的过程的Bean),所以在单例池中就找不到A,这个时候就又会使用BeanFactory去创建A,到这里就出现了一个死循环,也就是我们所说的循环依赖。
spring为了解决使用了三级缓存,首先我们要直到什么是三级缓存:
![](https://i-blog.csdnimg.cn/blog_migrate/297ccc0376574389f62e70c5ca19ebc0.png)
上面三个map,singletonObjects是一级缓存,earlySingletonObjects是二级缓存,singletonFactories是三级缓存
我们要先进入AbstractApplicationContext类中,找到this.finishBeanFactoryInitialization(beanFactory)这个方法。这个方法是用来完成bean工厂的初始化
接着对所有的beanName进行遍历,挨个实例化:
遍历的时候会判断该bean是由工厂bean创建的还是普通方式创建的,我们的是普通方式创建就调用getBean方法
实际的业务操作是由doGetBean()来完成,先检查缓存中是否有该bean,具体方法是从单例池中根据beanName获取对象,没有获取到然后判断该bean是不是正在创建,这里有个set集合用来存放正在创建的bean,在所有bean创建之前会将beanName添加到这个set中。这个时候set中没有A的name,所以spring中不存在A,开始创建A。调用createBean(),具体实现是doCraeteBean(),通过createBeanInstance()方法创建实例,就是在堆中开辟空间。
创建实例后,将beanName和一个lamda表达式getEarlyBeanReference()放到三级缓存
接下来就是populateBean阶段:将B作为属性给A赋值,调用applyPropertyValues()方法去应用属性值,在该方法中将所有的属性放到一个集合中然后遍历,挨个获取属性名称和属性值,这里的属性值是一个RuntimeBeanReference类型的对象。不是我们所要的B对象,需要对此进行解析,获取到B对象的name,将B的name作为参数到spring容器中去查找,调用getBean(),具体实现方法是doGetBean(),很显然是找不到这个bean的。然后与创建A实例相同的方式去创建B实例,依次执行craeteBean(),doCreateBean,createBeanInstance(),完成B的实例化过程。将beanName和一个lamda表达式放到三级缓存中去。
开始执行B的populateBean的阶段,与A的相同,需要A作为属性给B对象赋值,我们再执行一遍getBean这个流程,这是第三遍getBean(),与之前不同的是,从一级缓存取不到bean的时候,但另外一个条件A是正在创建的bean也成立了,所以进入if分支里面,依次从一级二级三级缓存中尝试获取bean,最后从三级缓存中取出之前存入的lamda表达式,调用它的getObject执行lamda表达式,暴露bean,遍历所有的后置处理器判断时候需要创建代理对象,有代理对象就将代理对象返回,没有就将原对象返回。这样我们就获取到了A对象的半成品,将其放到二级缓存,删除三级缓存。接着将二级缓存中的半成品bean作为属性填充给B。
到此为止就完成了B的实例化过程,将B放入一级缓存,删除二级和三级缓存。B的创建已经完成,这时就可以将B作为属性填充给A。A到此也完成了实例化过程,将A放入一级缓存,删除二级和三级缓存。A的创建也已经完成。
上面只是将A加入容器所执行的步骤,在最前面有对所有的beanName进行遍历操作,接下来需要将B加入容器中,因为可以直接从单例池获取到B所以接没有了后面的步骤。至此所有的Bean都已创建完成。
一级缓存可以解决循环依赖吗?不可以!虽然可以将bean的成品和半成品放到一个map中,然后用一个tag比如1或者2来区分他们是什么,但是!!!这样每次操作都需要进行先取值,再判断,再进行其它操作,为什么不直接使用两个map来存放呢? 更重要的是:我们不可以暴露半成品,因为半成品没有经历完整的生命周期,属性可能也没有赋值,这个时候让外界获取到了这种bean使用其中的属性,就会产生一些其他的问题。
二级缓存可以解决缓存依赖吗?如果没有加入aop可以!!!但是加入了aop就不行了,我们知道springaop发生在beanPostProcesser中的后置或者前置处理方法中,但是要执行后置处理方法那么该bean就必须是一个成品对象,但是当a作为属性给B对象填充的时候,a这个时候并不是一个成品bean,完成不了aop。只有将aop进行提前,才可以办到,这里往三级缓存中放入一个lamda表达式,它作为方法的参数时并不会执行,只有调用了它的getObject()才会执行lamda表达式,也就是在第三个doGetBean()里面,才进入了if分支从三级缓存取到lamda表达式进行执行。所以必须要使用三级缓存才能解决带有aop的循环依赖。