- 作者简介:大家好,我是五度鱼,一个普通的Java领域博主,不停输出Java技术博客和干货。
- 座右铭:锲而不舍,金石可镂。
- 个人主页:五度鱼学Java的主页
在Spring
框架中,循环依赖是一个非常常见的问题。当两个或多个bean
之间相互依赖时,就会产生循环依赖。这种情况可能会导致程序无限循环,从而导致性能问题和程序崩溃。
Spring
框架通过检测循环依赖并采取相应措施来解决这个问题。例如,使用懒加载的方式来避免循环依赖。但是,有时候我们的代码中可能会有一些错误,导致循环依赖的产生。
在这篇博客中,我们将深入探讨Spring
循环依赖的问题。我们将讨论如何产生循环依赖,以及如何避免和解决循环依赖。我们还将深入探讨Spring
框架如何检测和解决循环依赖。通过本文的阅读,你将能够更好地了解循环依赖的问题,并能够编写出更健壮的代码。
编写程序,测试一下在singleton+setter
的模式下产生的循环依赖,Spring
是否能够解决?
package com.powernode.spring6.bean;
/**
* @author 五度鱼
* @version 1.0
* @className Husband
* @since 1.0
**/
public class Husband {
private String name;
private Wife wife;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setWife(Wife wife) {
this.wife = wife;
}
// toString()方法重写时需要注意:不能直接输出wife,输出wife.getName()。要不然会出现递归导致的栈内存溢出错误。
@Override
public String toString() {
return "Husband{" +
"name='" + name + '\'' +
", wife=" + wife.getName() +
'}';
}
}
package com.powernode.spring6.bean;
/**
* @author 五度鱼
* @version 1.0
* @className Wife
* @since 1.0
**/
public class Wife {
private String name;
private Husband husband;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setHusband(Husband husband) {
this.husband = husband;
}
// toString()方法重写时需要注意:不能直接输出husband,输出husband.getName()。要不然会出现递归导致的栈内存溢出错误。
@Override
public String toString() {
return "Wife{" +
"name='" + name + '\'' +
", husband=" + husband.getName() +
'}';
}
}
<?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="husbandBean" class="com.powernode.spring6.bean.Husband" scope="singleton">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
</bean>
<bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="singleton">
<property name="name" value="小花"/>
<property name="husband" ref="husbandBean"/>
</bean>
</beans>
package com.powernode.spring6.test;
import com.powernode.spring6.bean.Husband;
import com.powernode.spring6.bean.Wife;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author 五度鱼
* @version 1.0
* @className CircularDependencyTest
* @since 1.0
**/
public class CircularDependencyTest {
@Test
public void testSingletonAndSet(){
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
可以解决这个问题。
再来测试一下:prototype+set
注入的方式下,循环依赖会不会出现问题?
<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="husbandBean" class="com.powernode.spring6.bean.Husband" scope="prototype">
<property name="name" value="张三"/>
<property name="wife" ref="wifeBean"/>
bean>
<bean id="wifeBean" class="com.powernode.spring6.bean.Wife" scope="prototype">
<property name="name" value="小花"/>
<property name="husband" ref="husbandBean"/>
bean>
beans>
执行测试程序:发生了异常,异常信息如下:
Caused by: org.springframework.beans.factory.**BeanCurrentlyInCreationException**: Error creating bean with name 'husbandBean': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:265)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:325)
... 44 more
翻译为:创建名为“husbandBean”
的bean
时出错:请求的bean当前正在创建中:是否存在无法解析的循环引用?
通过测试得知,当循环依赖的**所有Bean
的scope
="prototype
"的时候,产生的循环依赖,Spring
是无法解决的,会出现BeanCurrentlyInCreationException
**异常。
大家可以测试一下,以上两个Bean
,如果其中一个是singleton
,另一个是prototype
,是没有问题的。
为什么两个Bean
都是prototype
时会出错呢?
来测试一下singleton + 构造注入
的方式下,spring
是否能够解决这种循环依赖。
package com.powernode.spring6.bean2;
/**
* @author 五度鱼
* @version 1.0
* @className Husband
* @since 1.0
**/
public class Husband {
private String name;
private Wife wife;
public Husband(String name, Wife wife) {
this.name = name;
this.wife = wife;
}
// -----------------------分割线--------------------------------
public String getName() {
return name;
}
@Override
public String toString() {
return "Husband{" +
"name='" + name + '\'' +
", wife=" + wife +
'}';
}
}
package com.powernode.spring6.bean2;
/**
* @author 五度鱼
* @version 1.0
* @className Wife
* @since 1.0
**/
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;
}
@Override
public String toString() {
return "Wife{" +
"name='" + name + '\'' +
", husband=" + husband +
'}';
}
}
<?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="hBean" class="com.powernode.spring6.bean2.Husband" scope="singleton">
<constructor-arg name="name" value="张三"/>
<constructor-arg name="wife" ref="wBean"/>
</bean>
<bean id="wBean" class="com.powernode.spring6.bean2.Wife" scope="singleton">
<constructor-arg name="name" value="小花"/>
<constructor-arg name="husband" ref="hBean"/>
</bean>
</beans>
@Test
public void testSingletonAndConstructor(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring2.xml");
Husband hBean = applicationContext.getBean("hBean", Husband.class);
Wife wBean = applicationContext.getBean("wBean", Wife.class);
System.out.println(hBean);
System.out.println(wBean);
}
执行结果:发生了异常,信息如下:
Caused by: org.springframework.beans.factory.**BeanCurrentlyInCreationException**: Error creating bean with name 'hBean': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:355)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:227)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:325)
... 56 more
和上一个测试结果相同,都是提示产生了循环依赖,并且Spring是无法解决这种循环依赖的。
为什么呢?
主要原因是因为通过构造方法注入导致的:因为构造方法注入会导致实例化对象的过程和对象属性赋值的过程没有分离开,必须在一起完成导致的。
Spring
为什么可以解决set + singleton
模式下循环依赖?
根本的原因在于:这种方式可以做到将“实例化Bean
”和“给Bean
属性赋值”这两个动作分开去完成。
实例化Bean
的时候:调用无参数构造方法来完成。此时可以先不给属性赋值,可以提前将该Bean
对象“曝光”给外界。
给Bean
属性赋值的时候:调用setter方法来完成。
两个步骤是完全可以分离开去完成的,并且这两步不要求在同一个时间点上完成。
也就是说,Bean
都是单例的,我们可以先把所有的单例Bean
实例化出来,放到一个集合当中(我们可以称之为缓存),所有的单例Bean
全部实例化完成之后,以后我们再慢慢的调用setter
方法给属性赋值。这样就解决了循环依赖的问题。
那么在Spring
框架底层源码级别上是如何实现的呢?请看:
在以上类中包含三个重要的属性:
Cache of singleton objects: bean name to bean instance.
单例对象的缓存:key
存储bean
名称,value
存储Bean
对象【一级缓存】
Cache of early singleton objects: bean name to bean instance.
早期单例对象的缓存:key
存储bean
名称,value
存储早期的Bean
对象【二级缓存】
Cache of singleton factories: bean name to ObjectFactory.
单例工厂缓存:key
存储bean
名称,value
存储该Bean
对应的ObjectFactory
对象【三级缓存】
这三个缓存其实本质上是三个Map
集合。
我们再来看,在该类中有这样一个方法addSingletonFactory()
,这个方法的作用是:将创建Bean
对象的ObjectFactory
对象提前曝光。
再分析下面的源码:
从源码中可以看到,spring
会先从一级缓存中获取Bean
,如果获取不到,则从二级缓存中获取Bean
,如果二级缓存还是获取不到,则从三级缓存中获取之前曝光的ObjectFactory
对象,通过ObjectFactory
对象获取Bean
实例,这样就解决了循环依赖的问题。
总结:
Spring
只能解决setter
方法注入的单例bean
之间的循环依赖。ClassA
依赖ClassB
,ClassB
又依赖ClassA
,形成依赖闭环。Spring
在创建ClassA
对象后,不需要等给属性赋值,直接将其曝光到bean
缓存当中。在解析ClassA
的属性时,又发现依赖于ClassB
,再次去获取ClassB
,当解析ClassB
的属性时,又发现需要ClassA
的属性,但此时的ClassA
已经被提前曝光加入了正在创建的bean
的缓存中,则无需创建新的的ClassA
的实例,直接从缓存中获取即可。从而解决循环依赖问题。
最后求个关注,点赞,收藏,谢谢!