Spring是如何实现无代理对象的循环依赖
无代理对象的循环依赖
- 什么是循环依赖
- 解决方案
- 实现方式
- 测试验证
- 引入代理对象的影响
- 创建代理对象
- 问题分析
源码见: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
是原始对象。