Springboot 循环依赖

一、循环依赖

顾名思义多个类中的依赖形成了环路,形成了类似于死锁的情况,导致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生成了对象的代理**)**

Springboot 循环依赖_第1张图片

从日志中可以看到是 tPartnerOrgService 这个bean出现了循环依赖

我在tPartnerOrgService 中使用了@Aysnc注解 进行异步处理,而@Aysnc注解的bean生成了对象的代理,导致Spring bean最终加载的不是一个原始对象导致了此次问题的发生。

解决方案1:给 tPartnerOrgService 加上@Lazy注解

Springboot 循环依赖_第2张图片

解决方案2:代码优化,不要让@Async的Bean参与循环依赖

四、针对以上问题 对 Spring如何解决循环依赖进行详细阐述

Springboot 循环依赖_第3张图片

首先Spring维护了三个Map,也就是我们通常说的三级缓存

  • singletonObjects:俗称单例池,缓存创建完成的单例Bean
  • singletonFactories:映射创建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容器中增加了一个代理对象,也就是说beanAbeanB并不是一个原始对象,而是一个代理对象

  • 接下来进行执行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开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

你可能感兴趣的:(面试,学习路线,阿里巴巴,android,前端,后端)