1、什么是Spring中的循环依赖
循环依赖就是循环引用,也就是两个或者两个以上的Bean相互持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。
2、Spring处理循环依赖的机制
无法解决的循环依赖问题:
- 单例Bean构造函数的循环依赖
- prototype原型的循环依赖 (对与原型Bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx方法产生循环依赖,Spring都会直接报错处理BeanCurrentlyInCreationException)
- 构造器的循环依赖问题⽆法解决,只能拋出 BeanCurrentlyInCreationException 异常
解决单例Bean的属性注入的循环依赖问题
1、原理
在解决属性循环依赖时,spring采⽤的是提前暴露对象的⽅法。Spring 的循环依赖的理论依据基于 Java 的引⽤传递,当获得对象的引⽤时,对象的属性是可以延后设置的,但是构造器必须是在获取引⽤之前。Spring通过setXxx或者@Autowired⽅法解决循环依赖其实是通过提前暴露⼀个ObjectFactory对象来完成的,简单来说ClassA在调⽤构造器完成对象初始化之后,在调⽤ClassA的setClassB⽅法之前就把ClassA实例化的对象通过ObjectFactory提前暴露到Spring容器中。
- Spring容器初始化ClassA通过构造器初始化对象后提前暴露到Spring容器。
- ClassA调⽤setClassB⽅法,Spring⾸先尝试从容器中获取ClassB,此时ClassB不存在Spring
容器中。 - Spring容器初始化ClassB,同时也会将ClassB提前暴露到Spring容器中
- ClassB调⽤setClassA⽅法,Spring从容器中获取ClassA ,因为第⼀步中已经提前暴露了
ClassA,因此可以获取到ClassA实例
- ClassA通过spring容器获取到ClassB,完成了对象初始化操作。
- 这样ClassA和ClassB都完成了对象初始化操作,解决了循环依赖问题。
原理图大概如下:
3、Spring源码分析循环依赖问题
准备:创建了两个Bean,#CircularReferenceBean和#ItBean,两个bean相互依赖,通过setXxx方法进行注入。
1、以new ClassPathXmlApplicationContext("classpath:applicationContext.xml")为入口进行源码跟踪。进入到AbstractBeanFactory#doGetBean方法,获取Bean。
先1 2 3 级缓存中拿bean,如果没有拿到,则创建。具体的逻辑如下
由于是第一次创建CircularReferenceBean,所以缓存中并没有,需要创建,走下面流程,解析bean标签里面的依赖信息,然后调用创建bean的方法。AbstractBeanFactory#doGetBean.getSingleton(),进入createBean方法,查看具体的创建逻辑。
进入之后,里面有doCreateBean方法,这是真正执行操作的方法,Spring中,真的的逻辑操作的方法都是doXxx为前缀的方法。下面代码为处理循环依赖的主要逻辑代码。
先判断是否需要先将bean放入到三级缓存中,如果是需要则将bean放入三级缓存,注意,此时的bean还没有初始化完成的。里面的属性信息都没有进行赋值。进入populateBean()方法对Bean进行属性的装配,这里面完成对bean中注入属性的分析和赋值。进入方法,主要关注这一段代码。
可以看到 CircularReferenceBean创建的时候需要注入一个id为ItBean的bean。进入applyPropertyValues方法,看看如何获取需要的属性的。AbstractAutowireCapableBeanFactory#applyPropertyValues方法中调用BeanDefinitionValueResolver#resolveValueIfNecessary方法
进入resolveReference方法,方法中最终通过getBean的方法获取所需要的ItBean。进入方法,可以看到创建ItBean和创建CircularReferenceBean的方法一样,都进入到了doCreateBean方法。创建ItBean的流程跟上面流程完全一致,创建的时候发现ItBean依赖CircularReferenceBean则跟上述流程一样getBean方法获取CircularReferenceBean,并进入到doCreateBean方法中,但是这个时候由于CircularReferenceBean是被放入到三级缓存中的,所以可以再三级缓存中直接获取到,并且将CircularReferenceBean放到2级缓存中。这样ItBean就创建成功了,CircularReferenceBean也可以正常依赖ItBean。