Spring循环依赖

循环依赖就是循环引用,就是两个或者多个bean之间互相持有对方,比如A引用B,B引用C,C引用A。如下图


注:此处不是循环调用,循环调用是方法之间的调用,循环调用是无法解决的,除非有终结条件,否则就是死循环,最终会导致内存溢出。

spring循环依赖包括:构造器循环依赖、setter循环依赖。

public class A {
	private B b;
	
	public void a() {
		b.b();
	}

	public B getB() {
		return b;
	}

	public void setB(B b) {
		this.b = b;
	}
	
}
public class B {
	private C c;

	public void b() {
		c.c();
	}
	public C getC() {
		return c;
	}

	public void setC(C c) {
		this.c = c;
	}
}
public class C {
	private A a;
	
	public void c() {
		a.a();
	}
	public A getA() {
		return a;
	}

	public void setA(A a) {
		this.a = a;
	}
}

在spring中循环依赖分为三种情况:

1、构造器循环依赖

表示通过构造器注入构成的循环依赖,此依赖是无法解决,只能抛出BeanCurrentlyInCreationException异常表示循环依赖。

如果在创建A类时,构造器需要B类,接着创建B类又发现需要C类,则又去创建C,最终发现又需要A类,从而形成一个环,没办法创建。

spring容器将每一个正在创建的bean标识符放在一个“当前创建bean池”中,bean标识符在创建过程中将一直保持在这个池中,因此如果在创建bean过程中,发现自己已经在“当前创建bean池”里时,则抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的bean从“当前创建bean池”中清除掉。

我们通过一个测试案例来分析

(1)创建配置文件

<bean id="a" class="com.spring.test">
	<constructor=arg index="0" ref="b">
</bean>

<bean id="b" class="com.spring.test">
	<constructor=arg index="0" ref="c">
</bean>

<bean id="c" class="com.spring.test">
	<constructor=arg index="0" ref="a">
</bean>

(2)创建测试案例

public class TestApp {
	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("test.xml");
	}
}

针对以上代码分析:

a、spring创建"a" bean时,首先去“当前bean池”查找是否当前bean正在创建,如果没有发现,则继续准备其需要的构造器参数“B”,并将a标识符放到“当前bean池”;

b、spring创建"b" bean时,首先去“当前bean池”查找是否当前bean正在创建,如果没有发现,则继续准备其需要的构造器参数“C”,并将b标识符放到“当前bean池”;

c、spring创建"c" bean时,首先去“当前bean池”查找是否当前bean正在创建,如果没有发现,则继续准备其需要的构造器参数“A”,并将c标识符放到“当前bean池”;

d、到此为止,spring要去创建“a”bean,发现该bean标识符在“当前bean池”,表示循环依赖,抛出BeanCurrentlyInCreationException异常

2、setter循环依赖

表示通过setter注入方式构成的循环依赖,对于setter注入造成的依赖是通过spring容器提前暴露刚完成 构造器注入但未完成其他步骤(如setter注入)的bean来完成的,而且只能解决单例作用域的bean循环依赖。通过提前暴露一个单例工厂方法,从而使其他bean能引用到该bean,如下代码示例:

addSingletonFactory(beanName,new ObjectFactory() {
			public Object getObject() throw BeansException{
				return getEarlyBeanReference(beanName,mbd,bean);
			}
		});

具体步骤如下:

a、spring创建单例“a”bean时,首先根据无参构造器创建bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的bean,并将“a”标识符放到“当前创建bean池”,然后进行setter注入“b”;

b、spring创建单例“b”bean时,首先根据无参构造器创建bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的bean,并将“b”标识符放到“当前创建bean池”,然后进行setter注入“c”;

c、spring创建单例“c”bean时,首先根据无参构造器创建bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的bean,并将“c”标识符放到“当前创建bean池”,然后进行setter注入“a”时,由于提前暴露了“ObjectFactory”工厂,从而使用它返回提前暴露一个创建中的bean;

d、最后再注入“b”、和“a”,完成setter注入。

3、prototype范围的依赖处理

对于“prototype”作用域bean,spring容器无法完成依赖注入,因为spring容器不进行缓存“prototype”作用域的bean,因此无法提前暴露一个创建中的bean,示例如下:

(1)创建配置文件

<bean id="a" class="com.spring.test" scope="prototype">
	<property name="b" ref="b"/>
</bean>
<bean id="b" class="com.spring.test" scope="prototype">
	<property name="c" ref="c"/>
</bean>
<bean id="c" class="com.spring.test" scope="prototype">
	<property name="a" ref="a"/>
</bean>

(2)创建测试用例

public class TestApp {
	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("test.xml");
	}
}

对于“singleton”作用域的bean,可以通过“setAllowCircularReference(false);”来禁用循环引用。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值