文章目录
- 问题
- 解决方案
- 非正常表现
- 解决方案
这章我们主要来聊聊在
doCreateBean()
方法中,`spring是如何解决循环依赖的,以及一些错误的使用方式。
问题
假设现在我们有两个类,分别是A
类,B
类,同时A
类需要引用B
类,B
类需要引用A
类,也就是套娃模式。
@Component
public class A {
@Autowired
private B b;
}
@Component
public class B {
@Autowired
private A a;
}
那么Spring如何处理这种情况呢?
在这里我们重新回顾一下spring对于bean加载的流程。
存在
不存在
在BeanFactory中获取Bean
是否存在Bean
返回Bean
创建Bean
解决方案
其实前面的加载流程出来了解决方案就很简单了,只要在填充Bean属性
之间将直接放到BeanFactory
中就可以了,这样后面依赖到当前bean就可以直接从BeanFactory中取出,然后返回出去了,不用在走创建bean流程。
下面我们以Bean的视角来看下是如何解决的。
创建A实例
将A实例添加到BeanFactory
填充A属性触发创建B实例
创建B实例
填充B属性触发创建A实例
在BeanFactory中获取到A实例
这样就跳出了一直创建的死循环。
非正常表现
看了上面的解决方案是否觉得就安全了呢?就可以放心大胆的循环依赖Bean了?
下面我们在来看一个例子,还是基于上面的两个类,只做一些小小的改变。
@Component
public class A {
@Autowired
private B b;
@Async
public void async(){}
}
@Component
public class B {
@Autowired
private A a;
@Async
public void async(){}
}
我们只是在一个方法上添加了一个注解@Async
,然后情况就会出现翻天覆地的变化。
然后报错了,循环依赖,其中的bean使用的wrapper。
不是说的好好的,已经解决了循环依赖问题嘛?怎么又报出这个错误?
这个@Async
注解到底是什么妖魔鬼怪。
我们首先来看看,这个错误到底是哪个地方抛出来的。
下面这段代码发生在Bean创建完毕之后。
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
if (exposedObject == bean) {
exposedObject = earlySingletonReference;
}
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
String[] dependentBeans = getDependentBeans(beanName);
Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
if (!actualDependentBeans.isEmpty()) {
throw new BeanCurrentlyInCreationException(beanName,
"Bean with name '" + beanName + "' has been injected into other beans [" +
StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
"] in its raw version as part of a circular reference, but has eventually been " +
"wrapped. This means that said other beans do not use the final version of the " +
"bean. This is often the result of over-eager type matching - consider using " +
"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
}
}
}
}
还是分步走
- 第一步,通过BeanFactory获取到原始Bean
- 第二步,判断创建的Bean与获取的Bean是否相同(重要…有些同学灵光乍现了吗?),相同则直接返回创建的Bean
- 第三步,如果不允许注入wrappingBean并且当前Bean内部有依赖,而且依赖已经被使用了,就抛出了上面的错误。
如果是一般情况,我们在第二步就会直接返回,因为我们创建的Bean和原始的Bean是一模一样的。(也就是没有@Async
注解的时候,灵光乍现了嘛?)
那么反过来一想,使用@Async
注解就会导致我们创建的Bean和从BeanFactory
中获取到的原始Bean不一致。
为什么呢?
首先@Async
注解的作用就是将同步操作转换成异步操作,那么如何无侵入的实现呢? 代理对吧。 so,创建的Bean返回的就是代理类。 所以不一样。
所以只要注解会导致Bean被代理,就会导致这个问题。(循环依赖)
解决方案
所以怎么解决呢?
@Lazy
注解,由于spring的@Lazy
会让判断条件无法达成而跳过检测。
else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName))
也就是this.allowRawInjectionDespiteWrapping
的值会变为true。
因为@Lazy
注解也是返回的代理Bean,所以Spring让这个Bean跳过了本次检测。
实例:
@Component
public class A {
@Autowired
@Lazy
private B b;
@Async
public void async(){}
}
@Component
public class B {
@Autowired
@Lazy
private A a;
@Async
public void async(){}
}