spring 循环依赖注入
什么是循環依賴
循環依賴就是循環引用,就是兩個或多個Bean相互之間的持有對方,比如A引用B,B引用C,C引用A,則它們最終反映為一個環。
spring 中循環依賴注入分三種情況
1. 構造器循環依賴
2. setter方法循環注入
2.1 setter方法注入 單例模式(scope=singleton)
2.2 setter方法注入 非單例模式
我們首先創造3個互相依賴的bean類
A.java
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");} }B.java
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");} }C.java
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");} }執行類SpringMain.java
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();} }1. 構造器循環依賴
表示通過構造器注入構成的循環依賴,此依賴是無法解決的,只能拋出BeanCurrentlyInCreationException異常表示循環依賴。
* 在創建A類時,構造器需要B類,那將去創建B,
* 在創建B類時又發現需要C類,則又去創建C,
* 最后在創建C時發現又需要A;從而形成一個環,沒辦法創建。
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()方法報錯
2. setter方法循環注入
setter循環依賴:表示通過setter注入方式構成的循環依賴。
對于setter注入造成的依賴是通過Spring容器提前暴露剛完成構造器注入但未完成其他步驟(如setter注入)的Bean來完成的,而且只能解決單例作用域的Bean循環依賴。
2.1 setter方法注入 單例模式 (scope=”singleton”)
具體步驟如下:
1. Spring容器創建單例“A” Bean,首先根據無參構造器創建Bean,并暴露一個“ObjectFactory ”用于返回一個提前暴露一個創建中的Bean,并將“A” 標識符放到“當前創建Bean池”;然后進行setter注入“B”;
2. Spring容器創建單例“B” Bean,首先根據無參構造器創建Bean,并暴露一個“ObjectFactory”用于返回一個提前暴露一個創建中的Bean,并將“B” 標識符放到“當前創建Bean池”,然后進行setter注入“C”;
3. Spring容器創建單例“C” Bean,首先根據無參構造器創建Bean,并暴露一個“ObjectFactory ”用于返回一個提前暴露一個創建中的Bean,并將“C” 標識符放到“當前創建Bean池”,然后進行setter注入“A”;進行注入“A”時由于提前暴露了“ObjectFactory”工廠從而使用它返回提前暴露一個創建中的Bean;
4. 最后在依賴注入“B”和“A”,完成setter注入。
執行SpringMain.main()方法打印如下:
I am B2.2 非單例 setter 循環注入(scope=“prototype”)
對于“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()方法報錯
模擬 Spring 單例 setter 循環依賴實現
創建一個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,否則直接返回beanreturn 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;} }- singletonObjects:單例Bean的緩存池
- singletonFactories:單例Bean在創建之初過早的暴露出去的Factory,為什么采用工廠方式,是因為有些Bean是需要被代理的,總不能把代理前的暴露出去那就毫無意義了。
- earlySingletonObjects:執行了工廠方法生產出來的Bean,總不能每次判斷是否解決了循環依賴都要執行下工廠方法吧,故而緩存起來。
getA()方法、 getB()方法、 getC()方法 是為了模擬applicationContext.getBean() 方法獲取bean實例的。因為這里省略了xml配置文件,就把getBean() 方法拆分了三個方法。
這里的ObjectFactory有什么用呢,為什么不直接保留bean 實例對象呢?
spring源碼中是這樣實現的如下代碼:
從源碼中可以看出,這個ObjectFactory的作用是:如果bean配置了代理,則返回代理后的bean。
想了解更多精彩內容請關注我的公眾號
本人簡書blog地址:http://www.jianshu.com/u/1f0067e24ff8????
點擊這里快速進入簡書
GIT地址:http://git.oschina.net/brucekankan/
點擊這里快速進入GIT
總結
以上是生活随笔為你收集整理的spring 循环依赖注入的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: spring 之 import标签、al
- 下一篇: Junit 多线测试 问题