什么是循环依赖?当两个或多个bean出现相互引用且引用形成一个闭环的时候,就是循环依赖。例如ClassA中有ClassB,ClassB中有ClassC,ClassC中有ClassA,那么A->B->C-A就形成了一个闭环,在Spring中一共有三种循环依赖,构造器循环依赖
,Setter循环依赖
,和Prototype作用域的循环依赖
,对于这三种循环依赖,Spring并不是都解决的,通过一个表格来描述:
名称 | 是否可解决循环依赖 |
---|---|
构造器循环依赖 | 否 |
Setter循环依赖 | 是 |
Prototype作用域的循环依赖 | 否 |
Spring对于循环依赖的解决还是比较复杂的,在16–Spring创建Bean的准备工作(一),从缓存中获取单例bean我们提到过几个变量,其中有这样一个,
/** Cache of singleton objects: bean name to bean instance. */
/** 缓存beanName和bean实例 key-->beanName,value-->beanInstance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Cache of singleton factories: bean name to ObjectFactory. */
/** 缓存beanName和beanFactory key-->beanName,value-->beanFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
/** 缓存beanName和bean实例 key-->beanName,value-->beanInstance 该缓存主要为了解决bean的循环依赖引用 */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/** Set of registered singletons, containing the bean names in registration order. */
/** 缓存所有注册的单例beanName */
private final Set<String> registeredSingletons = new LinkedHashSet<>(256);
singletonFactories和earlySingletonObjects都是是缓存的beanName和bean实例之间的键值对,但是对于后者来说,当一个bean还在创建过程中的时候,我们就已经可以通过getBean方法去获取该bean的实例,这里大家一定有疑问,既然某个bean还在创建之中,为什么可以获取其实例呢?在前两个小节我们讲的是SpringBean的实例化,只是通过CGLIB或者反射创建了bean的实例,不代表整个bean的创建工作已经完成,Spring在bean的创建过程中会判断是否允许循环依赖,如果允许的话,那么不等该bean创建完成,就会将创建的bean实例缓存到earlySingletonObjects中(这里要将创建bean实例和创建完整的bean区分开来)。
我们还是通过一个例子来说明一下:
package com.lyc.cn.day11;
/**
* @author: LiYanChao
* @create: 2018-09-07 16:36
*/
public interface Animal {
void sayHello();
}
package com.lyc.cn.day11;
/**
* @author: LiYanChao
* @create: 2018-09-07 16:36
*/
public class Cat implements Animal {
/** 名称 **/
private String name;
/** 年龄 **/
private int age;
private Dog dog;
@Override
public void sayHello() {
System.out.println("\n大家好,我是一只名叫" + getName() + "的猫,我跟" + dog.getName() + "之间有循环依赖关系!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
}
package com.lyc.cn.day11;
/**
* @author: LiYanChao
* @create: 2018-09-13 17:41
*/
public class Dog implements Animal {
/** 名称 **/
private String name;
/** 年龄 **/
private int age;
private Cat cat;
@Override
public void sayHello() {
System.out.println("\n大家好,我是一只名叫" + getName() + "的狗,我跟" + cat.getName() + "之间有循环依赖关系!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
}
一个接口和两个实现类,其中Cat类引用了Dog类,Dog类引用了Cat类,这样就形成一个闭环,就是所谓的循环引用。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- cat bean 依赖dog-->
<bean id="cat" class="com.lyc.cn.day11.Cat">
<property name="name" value="美美"/>
<property name="age" value="3"/>
<property name="dog" ref="dog"/>
</bean>
<!--dog bean 依赖cat-->
<bean id="dog" class="com.lyc.cn.day11.Dog">
<property name="name" value="壮壮"/>
<property name="age" value="5"/>
<property name="cat" ref="cat"/>
</bean>
</beans>
Spring是不能解决构造器循环依赖的,这里我们也不做过多的演示,只演示Setter和Prototype两种即可
package com.lyc.cn.day11;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
/**
* @author: LiYanChao
* @create: 2018-09-07 16:43
*/
public class MyTest {
private XmlBeanFactory xmlBeanFactory;
@Before
public void initXmlBeanFactory() {
System.out.println("========测试方法开始=======\n");
xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("day11.xml"));
}
@After
public void after() {
System.out.println("\n========测试方法结束=======");
}
@Test
public void test() {
Cat cat = xmlBeanFactory.getBean("cat", Cat.class);
cat.sayHello();
Dog dog = xmlBeanFactory.getBean("dog", Dog.class);
dog.sayHello();
}
}
========测试方法开始=======
大家好,我是一只名叫美美的猫,我跟壮壮之间有循环依赖关系!
大家好,我是一只名叫壮壮的狗,我跟美美之间有循环依赖关系!
========测试方法结束=======
可以看到,对于单例bean之间的循环引用,通过Setter方法注入,是可以解决的,我们可以将bean的作用域改为Prototype,再来看一下运行结果。
========测试方法开始=======
========测试方法结束=======
// 异常信息太多了,就粘贴部分
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'cat': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:256)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:198)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:367)
... 40 more
当我们获取bean时,抛出了循环依赖的异常,Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'cat': Requested bean is currently in creation: Is there an unresolvable circular reference?
,其原因就是Spring不会缓存Prototype作用域的bean的实例,那么自然也就不会缓存到earlySingletonObjects中,也就无法解析循环依赖了。
这里我们一定要将创建一个完整的bean和创建一个bean的实例区分开来:
Setter循环依过程赖简要分析:
以上的内容纯属个人理解,如有不对的地方,欢迎指正!对于这一部分内容的源码分析,我们会在后面的章节中讲解。