顾名思义多个类中的依赖形成了环路,形成了类似于死锁的情况,导致springboot在启动时无法为我们创建Bean。通俗来说 就是beanA中依赖了beanB,beanB中也依赖了beanA。
**spring是支持循环依赖的,但是默认只支持单例的循环依赖,**如果bean中依赖了原型bean,则需要加上lookup方法。Spring会为我们解决循环依赖。
@Autowired是通过三级缓存来解决循环依赖的。
@Autowired进行属性注入可以解决循环依赖。原理是: Spring控制了bean的生命周期,先实例化bean,后注入bean的属性。 Spring中记录了正在创建中的bean(已经实例化但还没初始化完毕的bean),所以可以再注入属性是,从记录的ben中去依赖的对象。
相对而言,单纯使用构造器注入就无法解决循环依赖。因为在构造时就需要传入依赖的对象,导致无法实例化。但是 构造器注入可以使用@Lazy解决循环依赖,在实例化时,传入代理对象,真正使用时才会生成真正的对象。
1、使用构造方法的方式来注入依赖,并且类A中依赖类B,类B也同时依赖类A,这样两个类都无法正常进行Bean的创建,就会抛出异常:BeanCurrentlyInCreationException
@Component
public class A {
private B b;
@Autowired
public A(B b) {
this.b = b;
}
}
@Component
public class B {
private A a;
@Autowired
public B(A a) {
this.a = a;
}
}
解决方法之一:可以使用lazy注解,延迟加载依赖
@Component
public class A {
private B b;
@Autowired
@Lazy
public A(B b) {
this.b = b;
}
}
@Component
public class B {
private A a;
@Autowired
@Lazy
public B(A a) {
this.a = a;
}
}
@Aysnc注解
的bean生成了对象的代理**)**从日志中可以看到是 tPartnerOrgService 这个bean出现了循环依赖
我在tPartnerOrgService 中使用了@Aysnc注解 进行异步处理,而@Aysnc注解的bean生成了对象的代理,导致Spring bean最终加载的不是一个原始对象导致了此次问题的发生。
解决方案1:给 tPartnerOrgService 加上@Lazy
注解
解决方案2:代码优化,不要让@Async
的Bean参与循环依赖
首先Spring维护了三个Map,也就是我们通常说的三级缓存
singletonObjects
:俗称单例池,缓存创建完成的单例BeansingletonFactories
:映射创建Bean的原始工厂earlySingletonObjects
:映射Bean的早期引用,也就是说这个Map里的Bean不是完整的,只是完成了实例化,但还没有初始化Spring通过三级缓存解决了循环依赖,其中一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象earlySingletonObjects,三级缓存为早期曝光对象工厂(singletonFactories)。
当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象
。
当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。
紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!
简单一句话说:先去缓存里找Bean,没有则实例化当前的Bean放到Map,如果有需要依赖当前Bean的,就能从Map取到。
针对上面的
@Aysnc注解产生的循环依赖进行分析:
有@Aysnc注解
的bean最后生成了一个代理对象,我们结合Spring bean创建的流程来分析这次问题。
beanA
开始初始化,beanA
实例化完成后给beanA
的依赖属性beanB
进行赋值
beanB
开始初始化,beanB
实例化完成后给beanB
的依赖属性beanA
进行赋值
因为beanA
是支持循环依赖的,所以可以在earlySingletonObjects
中可以拿到beanA
的早期引用的,但是因为beanB
打了@Aysnc注解
并不能在earlySingletonObjects
中可以拿到早期引用
接下来执行执行initializeBean(Object existingBean, String beanName)
方法,这里beanA
可以正常实例化完成,但是因为beanB
打了@Aysnc注解
,所以向Spring IOC容器中增加了一个代理对象,也就是说beanA
的beanB
并不是一个原始对象,而是一个代理对象
接下来进行执行doCreateBean
方法时对进行检测,以下代码有所删减,只保留核心逻辑代码
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
throws BeanCreationException {
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 actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
// 重点在这里,这里会遍历所有依赖的bean,如果beanA依赖beanB和缓存中的beanB不相等
// 也就是说beanA本来依赖的是一个原始对象beanB,但是这个时候发现beanB是一个代理对象,就会增加到actualDependentBeans
for (String dependentBean : dependentBeans) {
if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
actualDependentBeans.add(dependentBean);
}
}
// 发现actualDependentBeans不为空,就发生了我们最开始截图的错误
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.");
}
}
}
}
// Register bean as disposable.
try {
registerDisposableBeanIfNecessary(beanName, bean, mbd);
}
catch (BeanDefinitionValidationException ex) {
throw new BeanCreationException(
mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
}
return exposedObject;
}
先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦