Spring之循环依赖

本文目的

  1. Spring循环依赖是什么?
  2. Spring循环依赖是怎么出现的?
  3. Spring循环依赖怎么解决呢?

说明

Spring的循环依赖,顾名思义相互之间有依赖,和鸡生蛋蛋生鸡一样。所以本文要分析它是怎么出现的,该怎么解决?

以及构造器依赖注入,循环依赖无解?傻傻分不清

Spring循环依赖是什么

也称为循环引用,是指两个或多个Bean之间相互依赖,形成一个环状依赖链。如:A依赖B,B依赖A

Spring循环依赖是怎么出现的?

从业务上看就是多个对象之间相互依赖,不知道谁应先创建好再创建下一个。从正常数学规律来看像阿拉伯数字一样1之后是2再3,一个接一个出现,很明显这是违背正常规律的所以也有人认为这是设计不合理,但系统一直在演进总会有些因为迎合需求或者追求快而舍弃合理设计。虽然重新拆分优化从业务逻辑解决,但往往成本很高,所以这就需要一种折中解决方案允许它存在,且不影响原有功能。我想Spring解决循环依赖也是综合这方面的考虑吧。

Spring循环依赖怎么解决呢?

从依赖关系看就是A依赖B,B依赖A,它们都是唯一的导致创建A时又要B才算完成,而创建B又要A才算完成进入环形链,无法结束。但这个的前提是A和B是唯一的,如果不是唯一的自然就打破了环的形成。(例子:需要A时创建A而A依赖B,B就创建B1,B1需要A则创建A1,反之也一样,A和B1依赖的A(A1)不是一个对象实例,每次获取都是新建,所以不会维护唯一性直接创建就行,因此也不需要缓存已创建实例和锁来维护唯一性,简单理解是就是业务逻辑原本是递归获取依赖,而没有完成实例化的不会放进缓存(只有一级)就死循环,现在获取依赖直接new+初始化)。 所以第一种解决方案就是使用原先型(Spring中Bean的Scope=prototype)

其实Spring自己在针对创建单例Bean的时候就有解决循环依赖,而它的方案是三级缓存,例如A依赖B,B依赖A,创建A时因为要创建B这时A是不完整的但为了创建B时知道A会创建出来所以创建A时提前暴露出来,让B创建时能获取到正在创建的A,从这里看是只需要两个缓存,那为什么Spring要用三级缓存?结合源码个人理解是,创建A时它自己不知道和谁会形成循环依赖所以默认需要提前暴露出来,那我已经new出来直接放二级缓存,B不也能获取到A了吗?这又涉及到代理类,因为A提前暴露的是刚new出来的还未检查是否需要代理,如果A需要代理放入二级缓存需要先产生代理类这也是可以的,但之所以引入三级缓存,从代码逻辑看从三级缓存获取Bean和正常非循环依赖生成Bean是低耦合的,且延迟在需要时才从三级缓存拿出来初始化,所以三级缓存是与正常逻辑解耦,是一种拓展设计。从另一个角度(生命周期)看因为调用了BeanPostProcessor接口实现类(后处理器),如果没用到就是直接初始化且调用后处理,调用顺序和生命周期相违背。

所以第二种解法是Spring自身的兼容但是它是有限制的通过注解和Setter方法注入是可以正常创建Bean,而构造器注入部分可以部分不可以
如:

a、A依赖B,是用注解/setter注入的,B依赖A是构造器注入的可以正常创建Bean;

b、如果A、B都是构造器注入则不行;

c、A依赖B是构造器注入,B依赖A时Setter注入也不行 a情况之所以可以是因为A比B先创建(Spring创建Bean和名字排序和名字有关,ASCII顺序)创建A时能用无参构造器反射创建,而b、c情况则需要有依赖值的有参构造器,且依赖值这时无法获取。

	// 阐述如果不要三级缓存,提前初始化是什么情况(个人理解)
	protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
	... 
	// 这里注释了提前缓存单例为了解决循环依赖
	// 假设我把getEarlyBeanReference(beanName, mbd, bean)直接执行(初始化)放入二级缓存(earlySingletonObjects)
	// 下一步就是populateBean(填充属性),initializeBean(初始化),这和上一步初始化定义上一样,这就看起很蒙,虽然initializeBean会过滤掉,
	// 但从阅读来讲,getEarlyBeanReference我是在有需要时才执行的,而不是在我走正常逻辑时也必须走形成强依赖
	
		// Eagerly cache singletons to be able to resolve circular references
		// even when triggered by lifecycle interfaces like BeanFactoryAware.
		boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
				isSingletonCurrentlyInCreation(beanName));
		if (earlySingletonExposure) {
			if (logger.isTraceEnabled()) {
				logger.trace("Eagerly caching bean '" + beanName +
						"' to allow for resolving potential circular references");
			}
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		// Initialize the bean instance.
		Object exposedObject = bean;
		try {
			populateBean(beanName, mbd, instanceWrapper);
			exposedObject = initializeBean(beanName, exposedObject, mbd);
		}
		catch (Throwable ex) {
			if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
				throw (BeanCreationException) ex;
			}
			else {
				throw new BeanCreationException(
						mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
			}
		}

		...

		return exposedObject;
	}

第三种方式是懒加载使用@Lazy注解(属性、setter方法、构造器都可)标识依赖注入是懒加载的,在给属性赋值时检查到时懒加载就会封装一个懒加载代理对象

org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency
	@Override
	@Nullable
	public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

		descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
		if (Optional.class == descriptor.getDependencyType()) {
			return createOptionalDependency(descriptor, requestingBeanName);
		}
		else if (ObjectFactory.class == descriptor.getDependencyType() ||
				ObjectProvider.class == descriptor.getDependencyType()) {
			return new DependencyObjectProvider(descriptor, requestingBeanName);
		}
		else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
			return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
		}
		else {
			//如果是懒加载属性会返回懒加载代理对象
			Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
					descriptor, requestingBeanName);
			if (result == null) {
				result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
			}
			return result;
		}
	}

org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver	
		@Override
	@Nullable
	public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
		return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null);
	}

总结

出现的原因:

Spring循环依赖因为对象之间相互依赖,形成环,且对象实例是单例时就会产生循环依赖。

解决方案有三种:

1、依赖对象实例是原型,每次获取都是不同的实例

2、注入方式,相互依赖之间使用属性+注解方式注入或者Setter方法注入,不要用构造器注入,虽然部分情况可以但是会因为创建顺序而导致部分不可以正常创建Bean

3、使用懒加载,@Lazy注解(属性、setter方法、构造器都可)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值