循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如A引用B,B引用C,C引用A,则它们最终反映为一个环。
spring 中循环依赖注入分三种情况
我们首先创造3个互相依赖的bean类
public class A {
private B b;
public A(){}
public A(B b){ this.b = b; }
public B getB() { return b; }
public void setB(B b) { this.b = b; }
public void hello(){ b.doHello(); }
public void doHello(){
System.out.println("I am A");
}
}
public class B {
private C c;
public B(){}
public B(C c){ this.c = c; }
public C getC() { return c; }
public void setC(C c) { this.c = c; }
public void hello(){ c.doHello(); }
public void doHello(){
System.out.println("I am B");
}
}
public class C {
private A a;
public C(){}
public C(A a){ this.a = a; }
public A getA() { return a; }
public void setA(A a) { this.a = a; }
public void hello(){ a.doHello(); }
public void doHello(){
System.out.println("I am C");
}
}
public class SpringMain {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean-circle.xml");
A a = A.class.cast(ac.getBean("a"));
a.hello();
}
}
表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyInCreationException异常表示循环依赖。
Spring容器将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。
<bean id="a" class="cn.com.infcn.test.A">
<constructor-arg ref="b" />
</bean>
<bean id="b" class="cn.com.infcn.test.B">
<constructor-arg ref="c" />
</bean>
<bean id="c" class="cn.com.infcn.test.C">
<constructor-arg ref="a" />
</bean>
执行SpringMain.main()方法报错
setter循环依赖:表示通过setter注入方式构成的循环依赖。 对于setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的Bean来完成的,而且只能解决单例作用域的Bean循环依赖。
具体步骤如下:
<bean id="a" class="cn.com.infcn.test.A">
<property name="b" ref="b"></property>
</bean>
<bean id="b" class="cn.com.infcn.test.B">
<property name="c" ref="c"></property>
</bean>
<bean id="c" class="cn.com.infcn.test.C">
<property name="a" ref="a"></property>
</bean>
执行SpringMain.main()方法打印如下:
I am B
对于“prototype”作用域Bean,Spring容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。
<bean id="a" class="cn.com.infcn.test.A" scope="prototype">
<property name="b" ref="b"></property>
</bean>
<bean id="b" class="cn.com.infcn.test.B" scope="prototype">
<property name="c" ref="c"></property>
</bean>
<bean id="c" class="cn.com.infcn.test.C" scope="prototype">
<property name="a" ref="a"></property>
</bean>
执行SpringMain.main()方法报错
创建一个ObjectFactory.java
public class ObjectFactory<T> {
private String className;
private T t;
public ObjectFactory(String className, T t) {
this.className = className;
this.t = t;
}
public T getObject() {
//如果该bean使用了代理,则返回代理后的bean,否则直接返回bean
return t;
}
}
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class Main {
// 单例Bean的缓存池
public static final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
//单例Bean在创建之初过早的暴露出去的Factory,为什么采用工厂方式,是因为有些Bean是需要被代理的,总不能把代理前的暴露出去那就毫无意义了。
public static final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
//执行了工厂方法生产出来的Bean,总不能每次判断是否解决了循环依赖都要执行下工厂方法吧,故而缓存起来。
public static final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
public static void main(String[] args) {
A a = (A)getA();
a.hello();
a = (A)getA();
a.hello();
B b = (B)getB();
b.hello();
C c = (C)getC();
c.hello();
}
//模拟 spring中 applicationContext.getBean("a")
public static Object getA(){
String beanName = "A";
Object singletonObject = getSingleton(beanName);
if(singletonObject == null){
A bean = new A();
singletonFactories.put(beanName, new ObjectFactory<A>(beanName, bean));
bean.setB((B)getB());
addSingleton("A", bean);
return bean;
}
return singletonObject;
}
//模拟 spring中 applicationContext.getBean("b")
public static Object getB(){
String beanName = "B";
Object singletonObject = getSingleton(beanName);
if(singletonObject == null){
B bean = new B();
singletonFactories.put(beanName, new ObjectFactory<B>(beanName, bean));
bean.setC((C)getC());
addSingleton(beanName, bean);
return bean;
}
return singletonObject;
}
//模拟 spring中 applicationContext.getBean("c")
public static Object getC(){
String beanName = "C";
Object singletonObject = getSingleton(beanName);
if(singletonObject == null){
C bean = new C();
singletonFactories.put(beanName, new ObjectFactory<C>(beanName, bean));
bean.setA((A)getA());
addSingleton(beanName, bean);
return bean;
}
return singletonObject;
}
public static void addSingleton(String beanName, Object singletonObject){
singletonObjects.put(beanName, singletonObject);
earlySingletonObjects.remove(beanName);
singletonFactories.remove(beanName);
}
public static Object getSingleton(String beanName){
Object singletonObject = singletonObjects.get(beanName);
if(singletonObject==null){
synchronized (singletonObjects) {
singletonObject = earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
earlySingletonObjects.put(beanName, singletonObject);
singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
}
getA()方法、 getB()方法、 getC()方法 是为了模拟applicationContext.getBean() 方法获取bean实例的。因为这里省略了xml配置文件,就把getBean() 方法拆分了三个方法。
这里的ObjectFactory有什么用呢,为什么不直接保留bean 实例对象呢? spring源码中是这样实现的如下代码:
从源码中可以看出,这个ObjectFactory的作用是:如果bean配置了代理,则返回代理后的bean。