Spring中循环依赖场景有:
(1)构造器的循环依赖
(2)field属性的循环依赖(注解注入和setter注入)。
对于构造器的循环依赖:这种依赖spring是处理不了的,直 接抛出BeanCurrentlylnCreationException异常。
对于单例模式下的setter循环依赖或者是注解注入:通过“三级缓存”处理循环依赖。
而对于那些非单例循环依赖也是无法处理。
我们知道Spring单例对象的初始化大略分为三步:
createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象
populateBean:填充属性,这一步主要是多bean的依赖属性进行填充
initializeBean:调用spring xml中的init 方法。完成初始化
循环依赖主要发生在bean的实例化阶段的构造器循环依赖和bean的初始化的属性填充阶段的field循环依赖。
接下来,我们具体看看spring是如何处理三种循环依赖的。
1.setter循环依赖
Spring为了解决单例的循环依赖问题,使用了三级缓存,singletonFactories ,earlySingletonObjects ,singletonObjects 这三个map。主要在于singletonFactories这个第三级cache。
【1.】===》首先说下:这三级缓存的作用分别是:
singletonFactories : 进入实例化阶段的单例对象工厂的cache (三级缓存)
earlySingletonObjects :完成实例化但是尚未初始化的,提前曝光的单例对象的Cache (二级缓存)
singletonObjects:完成初始化的单例对象的cache(一级缓存)
关于singletonFactories、earlySingletonObjects、singletonObjects这三个属性:
1.singletonFactories中存放的bean是通过构造器反射创建出来的bean,没有进行任何属性填充,是个半成品bean;
2.earlySingletonObjects中存放的也是半成品,只不过在singletonFactories的基础上多了一次Bean后置处理器的处理;
3.singletonObjects中存放的是最后完整的Bean,我们通常所说的Spring的Bean容器,可以简单理解为就是singletonObjects。
【2.】===》然后我们,
我们在创建bean的时候,会首先从singletonObjects中获取bean,如果获取不到,就从earlySingletonObjects中获取,如果还获取不到,就从singletonFactories中获取,所以当第一次调用getSingleton(beanName,allowEarlyReference)时,肯定返回null
【3.】===》最后举例分析下:
“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。
Spring在启动时,会在refresh()方法中创建所有的单例Bean,并完成自动装配。
这里假设首先对A进行单例构建,首先getBean,然后doGetBean,接着getSingleton,这个方法里会去从上面三个缓存中去获取实例,看看有没有,第一次一定没有,然后接着getSingleton的一个重载方法,在这个方法中会调用createBean()方法,createBean()方法会调用到doCreateBean()方法,在doCreateBean()方法中,会先实例化Bean(此时的Bean是一个半成品),然后将Bean通过addSingletonFactory()方法放入到singletonFactories属性中,接着调用populateBean()方法为Bean填充属性,这里就会为A填充属性B,在为A装配B属性时,由于B此时还没有被创建,所以又会调用到getBean(B)。既然调用到getBean()方法,那么就会同上面的逻辑一样,先调用getSingleton(userService)判断是否为空,此时仍然为空,因为是第一次获取B,所以接着会调用到createBean()、doCreateBean()。当进入到doCreateBean()当中时,同样也是先实例化B,然后将半成品的B放入到singletonFactories中,接着通过populateBean()方法来为B装配A属性。
在为B装配A属性时,又会调用到getBean(A)方法,然后调用doGetBean(A)。接下来一步就与之前不一样了,在doGetBean(A)中仍然是先调用getSingleton(A)方法,此时该方法不会返回null了,因为在第3步中,将A放入到了singletonFactories中,所以此时返回值不为空,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中。
二级缓存earlySingletonObjects在这里是什么作用?为什么是3级缓存,2级不行吗
首先,这个earlySingletonObjects中存放的也是半成品,只不过在singletonFactories的基础上多了一次Bean后置处理器的处理,也就是说有些被后置处理器处理的过的bean,会放到这里,其实他起到的作用和singletonObjects是一样,只是这里存放的bean是前置有特殊处理逻辑的bean实例,在对普通bean实例化初始化之前就操作完成了。
2.构造器循环依赖
this .singletonsCurrentlylnCreation.add(beanName)将当前正要创建的bean 记录在缓存中Spring 容器将每一个正在创建的bean 标识符放在一个“当前创建bean 池”中, bean 标识柏:在创建过程中将一直保持在这个池中,因此如果在创建bean 过程中发现自己已经在“当前创建bean 池” 里时,将抛出BeanCurrentlylnCreationException 异常表示循环依赖;而对于创建完毕的bean 将从“ 当前创建bean 池”中清除掉。
3.非单例循环依赖
对于“prototype”作用域bean, Spring 容器无法完成依赖注入,因为Spring 容器不进行缓 存“prototype”作用域的bean ,因此无法提前暴露一个创建中的bean 。