無代理對象的循環依賴
- 什么是循環依賴
- 解決方案
- 實現方式
- 測試驗證
- 引入代理對象的影響
- 創建代理對象
- 問題分析
源碼見:mini-spring
什么是循環依賴
循環依賴是指在對象創建過程中,兩個或多個對象相互依賴,導致創建過程陷入死循環。以下通過一個簡單的例子來說明:
public class A { @Autowired private B b; public void func() {} public B getB() { return b; } public void setB(B b) { this.b = b; }
}
public class B { @Autowired private A a; public A getA() { return a; } public void setA(A a) { this.a = a; }
}
在上述代碼中,類 A
依賴于 B
(通過屬性 b
),而類 B
又依賴于 A
(通過屬性 a
)。如果不加以處理,在創建 A
時會嘗試注入 B
,創建 B
時又需要注入 A
,從而形成死循環,導致程序無法正常運行。
解決方案
對于沒有代理對象的循環依賴問題,Spring 提供了一種簡單有效的解決方案:提前暴露 Bean。核心思想是在 Bean 實例化完成后(但尚未完成屬性注入),將其加入緩存,從而避免在屬性注入階段因循環依賴而導致的死循環。
實現方式
在 Spring 的 DefaultSingletonBeanRegistry
類中,引入二級緩存 earlySingletonObjects
,用于存儲提前暴露的 Bean 實例。雖然從實現角度看,將其放入一級緩存也可以解決問題,但 Spring 使用二級緩存是為了與已完全初始化的 Bean(存儲在一級緩存中)進行區分。
// 二級緩存,保存實例化后的 Bean
protected Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>();
接下來,修改單例 Bean 的獲取邏輯。在 getSingletonBean
方法中,首先從一級緩存 singletonObjects
中查找 Bean,若未找到,則嘗試從二級緩存 earlySingletonObjects
中獲取:
@Override
public Object getSingletonBean(String beanName) { Object singletonObject = singletonObjects.get(beanName); if (singletonObject == null) { singletonObject = earlySingletonObjects.get(beanName); } return singletonObject;
}
通過上述修改,一個基本的循環依賴解決方案即告完成。
測試驗證
以下是測試代碼,用于驗證循環依賴是否被成功解決:
@Test
public void testCircularReference() { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:circular-reference-without-proxy-bean.xml"); A a = applicationContext.getBean("a", A.class); B b = applicationContext.getBean("b", B.class); Assert.assertEquals(a.getB(), b);
}
在創建 A
對象時,Spring 會在實例化完成后將其加入二級緩存 earlySingletonObjects
。隨后在注入屬性時,需要創建 B
對象,而 B
依賴于 A
。此時,Spring 直接從二級緩存中獲取 A
實例,完成 B
的創建,并將 B
注入到 A
中,從而成功打破循環依賴。
引入代理對象的影響
如果 Bean 被代理(例如通過 AOP 實現),上述解決方案可能會失效。下面通過一個示例分析問題所在。
創建代理對象
假設對 A
對象應用代理,添加一個前置通知(Before Advice):
@Component
public class ABefpreAdvice implements MethodBeforeAdvice { @Override public void before(Method method, Object[] args, Object target) throws Throwable { System.out.println("before"); }
}
Spring 的 XML 配置如下,其中 A
被配置為通過 AOP 代理:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <bean id="b" class="org.qlspringframework.test.bean.B"> <property name="a" ref="a"/> </bean> <!-- A 被代理 --> <bean id="a" class="org.qlspringframework.test.bean.A"> <property name="b" ref="b"/> </bean> <bean class="org.qlspringframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/> <bean id="pointcutAdvisor" class="org.qlspringframework.aop.aspectj.AspectJExpressionPointcutAdvisor"> <property name="expression" value="execution(* org.qlspringframework.test.bean.A.func(..))"/> <property name="advice" ref="methodInterceptor"/> </bean> <bean id="methodInterceptor" class="org.qlspringframework.aop.framework.adapter.MethodBeforeAdviceInterceptor"> <property name="advice" ref="beforeAdvice"/> </bean> <bean id="beforeAdvice" class="org.qlspringframework.test.common.ABefpreAdvice"/>
</beans>
問題分析
在引入代理對象后,測試結果會發生變化。B
中注入的 A
實例是原始對象(實例化后但未完成初始化的對象),而從 Spring 容器中最終獲取的 A
是代理對象,二者不再是同一個對象。這是因為 Spring 的二級緩存機制保存的是未代理的 A
實例,而代理對象是在后續階段生成的。
因此,Assert.assertEquals(a.getB(), b)
可能失敗,因為 a
是代理對象,而 b
中持有的 a
是原始對象。