一、循环依赖
顾名思义多个类中的依赖形成了环路,形成了类似于死锁的情况,导致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如何解决循环依赖进行详细阐述
?首先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<String> 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;
}
|