Spring 如何解决循环依赖这是一个非常经典的面试问题,那么 Spring 是如何解决循环依赖问题的呢?又是否能够让其解决循环依赖的方法失效呢?
在 JAVA
原生中遇到循环依赖时可以通过如下步骤解决。
实例化 A 对象
实例化 B 对象
往 A 对象中设置 B 对象
往 B 对象中设置 A 对象
但是有另外一种特殊情况,A 的构造方法参数中包含了 B,B 的构造方法参数中包含了 A,这种情况称为构造方法循环依赖。由于 A 和 B 都需要在实例化对象时提供参数,所以这种循环依赖是无解的。
如上所述,Spring 也是无法解决构造方法循环依赖的,但是属性循环依赖在实际使用中我们可以看到 Spring 是可以解决的。
Spring 的解决流程与我们上述的步骤一致:
getBean——取得 A Bean,在 doCreateBean 方法中开始创建 Bean 操作。
createBeanInstance——实例化 A Bean。
populateBean——为 A Bean 设置参数,并调用 getBean 方法创建 B Bean。
== createBeanInstance——实例化 B Bean。
== populateBean——为 B Bean 设置参数,并调用 getBean 方法获得未构造完全的 A Bean。
…
经过以上流程,Spring 就解决了 Bean 的循环依赖,这里面涉及到一个比较关键的方法 getSingleton(String beanName, boolean allowEarlyReference)
。
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// Quick check for existing instance without full singleton lock
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
synchronized (this.singletonObjects) {
// Consistent creation of early reference within full singleton lock
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
以上代码涉及到了三级缓存的概念,singletonObjects
是第一级缓存,存储了创建好的 Bean;earlySingletonObjects
是第二级缓存,存储了未初始化完成的 Bean;singletonFactories
是第三级缓存,存储了 Bean 工厂。
要解决循环依赖问题,就需要提前暴露未完成属性设值注入的未完成 Bean,否则就无法解决循环依赖。而从上述代码可知,allowEarlyReference
参数是决定是否提前暴露未完成 Bean 的关键参数。
从上述代码也可知,其实只需要二级缓存 earlySingletonObjects 就可以解决循环依赖问题,并不需要用到第三级缓存。
其实三级缓存是为了解决一个特殊场景的应用的,那就是在 AOP
代理情况下的循环依赖,详细内容在下文继续介绍。
默认的 Bean 工厂 allowEarlyReference
参数默认是 true
,所以可以提前暴露未完成 Bean,进而可以解决循环依赖问题。
public Object getSingleton(String beanName) {
return getSingleton(beanName, true);
}
如果我们复写这个 Bean 工厂,将 allowEarlyReference
修改为 false
,那么 Spring 就无法解决循环依赖问题了。
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(new DefaultListableBeanFactory(){
@Override
public Object getSingleton(String beanName) {
return super.getSingleton(beanName, false);
}
});
还有一种更简单的方法,其实 Spring
的 BeanFactory
提供了关闭循环依赖的接口。
将 setAllowCircularReferences
设置为 false
之后,Spring
不会往三级缓存中注入 Bean
工厂,所以也就无法解决循环依赖。
// Eagerly cache singletons to be able to resolve circular references
// even when triggered by lifecycle interfaces like BeanFactoryAware.
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
配置方法如下:
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.setAllowCircularReferences(false);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(beanFactory);
以上修改都将导致循环依赖抛出异常:
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'oneBean': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'twoBean': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'oneBean': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:321)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1425)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:227)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1175)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:420)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1127)
at com.nineya.spring.bean.cycle.BeanCycleMain.run(BeanCycleMain.java:16)
at com.nineya.spring.bean.cycle.BeanCycleMain.main(BeanCycleMain.java:31)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'twoBean': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'oneBean': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:321)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1425)
......