上一篇:(八)Bean的生命周期
spring6里程碑版本的仓库
依赖:spring context依赖、junit依赖、log4j2依赖
log4j2.xml文件放到类路径下。
A对象中有B属性。B对象中有A属性。这就是循环依赖。我依赖你,你也依赖我。
比如:丈夫类Husband,妻子类Wife。Husband中有Wife的引用。Wife中有Husband的引用。
我们来编写程序,测试一下在singleton+setter的模式下产生的循环依赖,Spring是否能够解决?
创建Bean:丈夫类Husband
/*** 丈夫类*/
public class Husband {private String name;private Wife wife;@Overridepublic String toString() {return "Husband{" +"name='" + name + '\'' +", wife=" + wife.getName() +'}';}public String getName() {return name;}public void setName(String name) {this.name = name;}public void setWife(Wife wife) {this.wife = wife;}
}
创建Bean:妻子类Wife
/*** 妻子类*/
public class Wife {private String name;private Husband husband;public void setName(String name) {this.name = name;}public void setHusband(Husband husband) {this.husband = husband;}public String getName() {return name;}@Overridepublic String toString() {return "Wife{" +"name='" + name + '\'' +", husband=" + husband.getName() +'}';}
}
创建spring.xml配置:
测试程序:
@Testpublic void testSingletonSet(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);System.out.println(husbandBean);System.out.println(wifeBean);}
在singleton + set注入的情况下,循环依赖是没有问题的。Spring可以解决这个问题。、
怎么解决问题呢,先这样解释:(后面有底层解释,先这样理解)
Spring在解析xml文件的时候,遇见beanA,会先把beanA创建出来,然后发现beanA的属性需要beanB
然后Spring会去找beanB,发现beanB也是单例的,就会把对象先创建出来。
主要原因是,在这种模式下Spring对Bean的管理主要分为清晰的两个阶段:
核心的原因就是:实例化对象和对象的属性赋值分为两个阶段完成。
在singleton + set注入的情况下,循环依赖是没有问题的。Spring可以解决这个问题。
我们再来测试一下:prototype+set注入的方式下,循环依赖会不会出现问题?
还是那两个夫妻Bean
然后创建一个新的spring配置文件:spring1.xml
测试程序:
@Testpublic void testPrototypeSet(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring1.xml");Husband husbandBean = applicationContext.getBean("husbandBean", Husband.class);Wife wifeBean = applicationContext.getBean("wifeBean", Wife.class);System.out.println(husbandBean);System.out.println(wifeBean);}

翻译为:创建名为“husbandBean”的bean时出错:请求的bean当前正在创建中:是否存在无法解析的循环引用?
会出现异常BeanCreationException,当前bean正处于创建中异常,
也就是说bean处于第二步(populateBean:填充属性)就卡住了
如果我们把其中一个Bean改为单例的试试
再次运行,发现运行正常。

我们再来测试一下构造注入的方式下,spring是否能够解决这种循环依赖。
修改丈夫类:
/*** 丈夫类*/
public class Husband {private String name;private Wife wife;@Overridepublic String toString() {return "Husband{" +"name='" + name + '\'' +", wife=" + wife.getName() +'}';}public String getName() {return name;}public Husband(String name, Wife wife) {this.name = name;this.wife = wife;}
}
修改妻子类:
/*** 妻子类*/
public class Wife {private String name;private Husband husband;public Wife(String name, Husband husband) {this.name = name;this.husband = husband;}public String getName() {return name;}@Overridepublic String toString() {return "Wife{" +"name='" + name + '\'' +", husband=" + husband.getName() +'}';}
}
创建spring2.xml配置:
测试程序:
@Testpublic void testSingletonConstructor(){ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring2.xml");Husband husband = applicationContext.getBean("husband", Husband.class);Wife wife = applicationContext.getBean("wife", Wife.class);System.out.println(husband);System.out.println(wife);}
出现异常

修改spring2.xml:
再次运行测试程序,发现出现异常:

主要原因是因为通过构造方法注入导致的:因为构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分离开,必须在一起完成导致的。
Spring的循环依赖的理论依据其实是基于Java的引用传递,当我们获取到对象的引用时,实例化Bean的时候:调用无参数构造方法来完成。对象的属性是可以延后设置的。
通过Bean的生命周期我们知道,Spring的单例对象完成初始化主要分为三步:
循环依赖主要发生在第一、第二步。
对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,先把所有的单例Bean实例化出来,放到一个集合当中(我们可以称之为缓存)。
Spring为了解决单例的循环依赖问题,使用了三级缓存。
Spring在DefaultSingletonBeanRegistry这个类设置了三级缓存:
private final Map singletonObjects = new ConcurrentHashMap(256);private final Map> singletonFactories = new HashMap(16);private final Map earlySingletonObjects = new ConcurrentHashMap(16);
这三个Map的key都是存储Bean的id,
这三级缓存分别指:
首先在创建BeanA对象的时候,会把BeanA对象的工厂存入(曝光)在三级缓存里面。
解决循环依赖底层的一个代码是DefaultSingletonBeanRegistry的getSingleton方法:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {ObjectFactory> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return (singletonObject != NULL_OBJECT ? singletonObject : null);}
Spring使用三个判断,分别从缓存的一级读取到三级:
如果获取到了则:从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。
那么BeanA和BeanB的循环依赖完整的解决是:
beanA首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories中,此时进行初始化的第二步,发现自己依赖对象beanB此时就尝试去获取beanB,发现beanB还没有被创建,所以走beanB创建流程,beanB在初始化第一步的时候发现自己依赖了对象beanA,于是尝试获取beanA,尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全)尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于beanA通过ObjectFactory将自己提前曝光了,所以beanB能够通过ObjectFactory拿到beanA对象beanB拿到beanA对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回beanA中,beanA此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终beanA也完成了初始化,进去了一级缓存singletonObjects中,于是beanB拿到了beanA的对象引用,所以beanB的beanA对象完成了初始化。