【spring学习笔记 七】循环依赖解决思路


这章我们主要来聊聊在 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.");
					}
				}
			}
		}

还是分步走

  1. 第一步,通过BeanFactory获取到原始Bean
  2. 第二步,判断创建的Bean与获取的Bean是否相同(重要…有些同学灵光乍现了吗?),相同则直接返回创建的Bean
  3. 第三步,如果不允许注入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(){}

}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

唐芬奇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值