5.6循环依赖
5.6.1什么是循环依赖、循环调用
循环依赖:实例化bean是一个复杂的过程。循环依赖就是两个或者多个bean相互之间持有对方,比如TestA引用TestB,TestB引用TestA,则它们最终反映为一个环。
循环调用:循环调用是方法之间的环调用。循环调用是无法解决的,除非有终结条件,否则就是死循环,最终导致内存溢出。
5.6.2Spring如何解决循环依赖
首先我们定义循环依赖类、注入、测试方法
package CircularReference; public class TestA { private TestB testB; public TestA(){ } public TestA(TestB testB) { this.testB = testB; } public TestB getTestB() { return testB; } public void setTestB(TestB testB) { this.testB = testB; } } package CircularReference; public class TestB { private TestA testA; public TestB(){ } public TestB(TestA testA) { this.testA = testA; } public TestA getTestA() { return testA; } public void setTestA(TestA testA) { this.testA = testA; } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--构造器循环依赖--> <!--<bean id="testA" class="CircularReference.TestA">--> <!--<constructor-arg ref="testB"></constructor-arg>--> <!--</bean>--> <!--<bean id="testB" class="CircularReference.TestB">--> <!--<constructor-arg ref="testA"></constructor-arg>--> <!--</bean>--> <!--setter循环依赖--> <bean id="testA" class="CircularReference.TestA"> <property name="testB" ref="testB"></property> </bean> <bean id="testB" class="CircularReference.TestB"> <property name="testA" ref="testA"></property> </bean> </beans>
/** * 构造器循环依赖 */ @Test public void constructor(){ try { new ClassPathXmlApplicationContext("/circularReference.xml"); }catch (Exception e){ e.printStackTrace(); System.out.println(e.getCause()); } } /** * setter循环依赖 */ @Test public void setter(){ ApplicationContext contex = new ClassPathXmlApplicationContext("/circularReference.xml"); System.out.println(contex.getBean("testA")); }
3种循环依赖
1)构造器循环依赖(单例的)
构造器注入的循环依赖,此依赖是无法解决的。只能抛出BeanCurrentlyInCreationEx-ception异常表示循环依赖。与bean实例化有关。
代码中的实现方式:DefaultSingletonBeanRegistry中getSingleton(beanName-->,singletonFactory)方法的beforeSingletonCreation中的this.singletonsCurrently-InCreation.add(beanName)。5.4获取单例---大致逻辑---第3条。
Spring容器将每一条正在创建的bean标识符放在一个“当前创建bean池”中,bean标识符在创建过程中将一直保持在这个池中,因为如果在创建bean过程中发现自己已经在“当前创建bean池”里面时,抛出BeanCurrentlyInCreationEx-ception异常表示循环依赖。而对于创建完毕的bean将从“当前创建bean池”中移除。
模拟执行:
Spring容器创建“testA” bean,首先去“当前创建 bean 池” 查找是否当前 bean 正在创建,如果没发现,则继续准备其需妥的构造器参数“testB”,并将“testA”标识符放到“当前创建池”中。
Spring容器创建“testB”bean,首先去“当前创建 bean池”查找是否当前 bean 正在创建 ,如没发现,则继续准备其需妥 的构造 器参数“ testA”,并将“ testB”标识符放到“当前创建bean池”。
到此为止 Spring 容器要去创建“testA” bean,发现该 bean 标识符在“当前创建 bean 池”中,因为表示循环依赖,抛出 BeanCurrentlyInCreationException
2)setter循环依赖(单例的)
简单来说是通过一个默认构造器,并暴露一个FactoryBean用于返回一个创建中的bean(AbstractBeanFactory.doGetBean方法的getSingleton会用到),然后再进行setter设置。
通过setter注入方式构成的循环依赖。对于setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注入(默认构造器)但未完成其他步骤(如setter注入)的bean来完成的,而且只能解决单例作用域的bean循环依赖。通过提前暴露一个单例工厂方法,从而使其他bean能引用到该bean。
代码中的实现方式:
获取:AbstractBeanFactory-->doGetBean-->getSingleton
protected Object getSingleton(String beanName, boolean allowEarlyReference) { // Quick check for existing instance without full singleton lock //检查缓存中是否存在实例 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { //缓存中没有实例但是又在创建中,那么可能是循环依赖,解决循环依赖的方式是 //Spring创建bean的原则是不等bean创建完成就会创建bean的ObjectFactory提早曝光 singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { //如果为空,则锁定全局变量并进行处理 synchronized (this.singletonObjects) { // Consistent creation of early reference within full singleton lock singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null) { //当某些方法需要提前初始化的时候则会调用addSingletonFactory方法将对应的 //ObjectFactory初始化策略存储在singletonFactories ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { //调用预先设定的getObject方法 singletonObject = singletonFactory.getObject(); //记录在缓存中,earlySingletonObjects和singletonFactories互斥 this.earlySingletonObjects.put(beanName, singletonObject); this.singletonFactories.remove(beanName); } } } } } } return singletonObject; }
存储:AbstractAutowireCapableBeanFactory-->createBean-->doCreateBean-->addSingletonFactory
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { if (!this.singletonObjects.containsKey(beanName)) { this.singletonFactories.put(beanName, singletonFactory); this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } }
3)prototype范围的依赖处理
对于prototype作用域bean,Spring容器无法完成依赖注入,因为Spring容器不进行缓存“prototype”作用域的bean。因此无法暴露一个创建中的bean。
对于singleton作用域bean,可以通过“setAllowCircularReferences(false)”来禁止循环引用。
博客:Spring中循环引用的处理
https://www.iflym.com/index.php/code/201208280001.html

浙公网安备 33010602011771号