第一種:構造器參數循環依賴
第二種:setter方式單例,默認方式
第三種:setter方式原型,prototype
《Netty 實現原理與源碼解析 —— 精品合集》
《Spring 實現原理與源碼解析 —— 精品合集》
《MyBatis 實現原理與源碼解析 —— 精品合集》
《Spring MVC 實現原理與源碼解析 —— 精品合集》
《Spring Boot 實現原理與源碼解析 —— 精品合集》
《資料庫實體設計合集》
《Java 面試題 —— 精品合集》
《Java 學習指南 —— 精品合集》
引言:循環依賴就是N個類中循環嵌套引用,如果在日常開發中我們用new 對象的方式發生這種循環依賴的話程序會在運行時一直循環調用,直至內存溢出報錯。下面說一下Spring是如果解決循環依賴的。
第一種:構造器參數循環依賴
Spring容器會將每一個正在創建的Bean 標識符放在一個「當前創建Bean池」中,Bean標識符在創建過程中將一直保持在這個池中。
因此如果在創建Bean過程中發現自己已經在「當前創建Bean池」裏時將拋出BeanCurrentlyInCreationException異常表示循環依賴;而對於創建完畢的Bean將從「當前創建Bean池」中清除掉。
首先我們先初始化三個Bean。
public class StudentA {
private StudentB studentB ;
public void setStudentB(StudentB studentB) {
this.studentB = studentB;
}
public StudentA() {
}
public StudentA(StudentB studentB) {
this.studentB = studentB;
}
}
public class StudentB {
private StudentC studentC ;
public void setStudentC(StudentC studentC) {
this.studentC = studentC;
}
public StudentB() {
}
public StudentB(StudentC studentC) {
this.studentC = studentC;
}
}
public class StudentC {
private StudentA studentA ;
public void setStudentA(StudentA studentA) {
this.studentA = studentA;
}
public StudentC() {
}
public StudentC(StudentA studentA) {
this.studentA = studentA;
}
}
OK,上面是很基本的3個類,,StudentA有參構造是StudentB。StudentB的有參構造是StudentC,StudentC的有參構造是StudentA ,這樣就產生了一個循環依賴的情況,
我們都把這三個Bean交給Spring管理,並用有參構造實例化。
<bean id="a" class="com.zfx.student.StudentA">
<constructor-arg index="0" ref="b"></constructor-arg>
</bean>
<bean id="b" class="com.zfx.student.StudentB">
<constructor-arg index="0" ref="c"></constructor-arg>
</bean>
<bean id="c" class="com.zfx.student.StudentC">
<constructor-arg index="0" ref="a"></constructor-arg>
</bean>
下面是測試類:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("com/zfx/student/applicationContext.xml");
//System.out.println(context.getBean("a", StudentA.class));
}
}
執行結果報錯信息為:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name a: Requested bean is currently in creation: Is there an unresolvable circular reference?
如果大家理解開頭那句話的話,這個報錯應該不驚訝,Spring容器先創建單例StudentA,StudentA依賴StudentB,然後將A放在「當前創建Bean池」中,此時創建StudentB,StudentB依賴StudentC ,然後將B放在「當前創建Bean池」中,此時創建StudentC,StudentC又依賴StudentA, 但是,此時Student已經在池中,所以會報錯,,因為在池中的Bean都是未初始化完的,所以會依賴錯誤 ,(初始化完的Bean會從池中移除)
第二種:setter方式單例,默認方式
如果要說setter方式注入的話,我們最好先看一張Spring中Bean實例化的圖
如圖中前兩步驟得知:Spring是先將Bean對象實例化之後再設置對象屬性的
修改配置文件為set方式注入
<!--scope="singleton"(默認就是單例方式) -->
<bean id="a" class="com.zfx.student.StudentA" scope="singleton">
<property name="studentB" ref="b"></property>
</bean>
<bean id="b" class="com.zfx.student.StudentB" scope="singleton">
<property name="studentC" ref="c"></property>
</bean>
<bean id="c" class="com.zfx.student.StudentC" scope="singleton">
<property name="studentA" ref="a"></property>
</bean>
下面是測試類:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("com/zfx/student/applicationContext.xml");
System.out.println(context.getBean("a", StudentA.class));
}
}
列印結果為:
com.zfx.student.StudentA@1fbfd6
為什麼用set方式就不報錯了呢 ?
我們結合上面那張圖看,Spring先是用構造實例化Bean對象 ,此時Spring會將這個實例化結束的對象放到一個Map中,並且Spring提供了獲取這個未設置屬性的實例化對象引用的方法。
結合我們的實例來看,,當Spring實例化了StudentA、StudentB、StudentC後,緊接著會去設置對象的屬性,此時StudentA依賴StudentB,就會去Map中取出存在裡面的單例StudentB對象,以此類推,不會出來循環的問題嘍、
下面是Spring源碼中的實現方法。以下的源碼在Spring的Bean包中的DefaultSingletonBeanRegistry.java類中
/** Cache of singleton objects: bean name --> bean instance(緩存單例實例化對象的Map集合) */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
/** Cache of singleton factories: bean name --> ObjectFactory(單例的工廠Bean緩存集合) */
private final Map<String, ObjectFactory> singletonFactories = new HashMap<String, ObjectFactory>(16);
/** Cache of early singleton objects: bean name --> bean instance(早期的單身對象緩存集合) */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
/** Set of registered singletons, containing the bean names in registration order(單例的實例化對象名稱集合) */
private final Set<String> registeredSingletons = new LinkedHashSet<String>(64);
/**
* 添加單例實例
* 解決循環引用的問題
* Add the given singleton factory for building the specified singleton
* if necessary.
* <p>To be called for eager registration of singletons, e.g. to be able to
* resolve circular references.
* @param beanName the name of the bean
* @param singletonFactory the factory for the singleton object
*/
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);
}
}
}
第三種:setter方式原型,prototype
修改配置文件為:
<bean id="a" class="com.zfx.student.StudentA" scope="prototype">
<property name="studentB" ref="b"></property>
</bean>
<bean id="b" class="com.zfx.student.StudentB" scope="prototype">
<property name="studentC" ref="c"></property>
</bean>
<bean id="c" class="com.zfx.student.StudentC" scope="prototype">
<property name="studentA" ref="a"></property>
</bean>
scope="prototype" 意思是 每次請求都會創建一個實例對象。
兩者的區別是:有狀態的bean都使用Prototype作用域,無狀態的一般都使用singleton單例作用域。
測試用例:
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("com/zfx/student/applicationContext.xml");
//此時必須要獲取Spring管理的實例,因為現在scope="prototype" 只有請求獲取的時候才會實例化對象
System.out.println(context.getBean("a", StudentA.class));
}
}
列印結果:
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name a: Requested bean is currently in creation: Is there an unresolvable circular reference?
為什麼原型模式就報錯了呢 ?
對於「prototype」作用域Bean,Spring容器無法完成依賴注入,因為「prototype」作用域的Bean,Spring容器不進行緩存,因此無法提前暴露一個創建中的Bean。
來源:http:// t.cn/ExoH48x
推薦閱讀: