【spring】如何解决循环依赖

什么是循环依赖

  • A对象依赖了B对象,B对象依赖了A对象。
  • 在Spring中,一个对象并不是简单new出来了,而是会经过一系列的Bean的生命周期(核心是属性方法的依赖注入),导致在创建Bean的过程中,出现了互相等待的情况。
  • ABean创建-->依赖了B属性-->触发BBean创建--->B依赖了A属性--->需要ABean(但ABean还在创建过程中):这时候就会导致A和B都创建不出来。
  • 最简单的解决循环依赖的方式:@Lazy注解,直接生成空的代理对象赋值!
  • 俩个互相依赖的Bean都是原形的,循环依赖无法解决!因为原形的不能用之前创建的对象(每次都是一个新对象)!
  • 构造方法的方式去循环依赖,因为无法将构造方法实例化,所以循环依赖无法解决!

解决循环依赖的思路:打破循环,让一个对象创建出来

解决循环依赖思路.png

简单理解spring是如何解决循环依赖的:三级缓存

  • singletonObjects:单例池,已经进过完整生命周期。
  • earlySingletonObjects:早期的单例池,生命周期不完整。
  • singletonFactories:出现循环依赖后,他去存放如何创建earlySingletonObjects的具体实现。

深入理解spring是如何解决循环依赖的:三级缓存

  • singletonObjects:缓存的是已经经历了完整生命周期的bean对象。
  • earlySingletonObjects:缓存未经过完整生命周期的bean,如果某个bean出现了循环依赖,就会提前把这个暂时未经过完整生命周期的bean放入earlySingletonObjects中,这个bean如果要经过AOP,那么就会把代理对象放入earlySingletonObjects中,否则就是把原始对象放入earlySingletonObjects,但是不管怎么样,就是是代理对象,代理对象所代理的原始对象也是没有经过完整生命周期的,所以放入earlySingletonObjects我们就可以统一认为是未经过完整生命周期的bean。
  • singletonFactories:缓存的是一个ObjectFactory,也就是一个Lambda表达式。在每个Bean的生成过程中,经过实例化得到一个原始对象后,都会提前基于原始对象暴露一个Lambda表达式,并保存到三级缓存中,这个Lambda表达式可能用到,也可能用不到,如果当前Bean没有出现循环依赖,那么这个Lambda表达式没用,当前bean按照自己的生命周期正常执行,执行完后直接把当前bean放入singletonObjects中,如果当前bean在依赖注入时发现出现了循环依赖(当前正在创建的bean被其他bean依赖了),则从三级缓存中拿到Lambda表达式,并执行Lambda表达式得到一个对象,并把得到的对象放入二级缓存((如果当前Bean需要AOP,那么执行lambda表达式,得到就是对应的代理对象,如果无需AOP,则直接得到一个原始对象))。

一级缓存singletonObjects源码分析

// spring启动的时候会调用doGetBean
protected <T> T doGetBean(
		String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
		throws BeansException {
	.......
	// 从单例池去找Bean,一二级缓存找不到会去执行三级缓存的lambda表达式,并放入二级缓存!
	Object sharedInstance = getSingleton(beanName);
	......
}

二级缓存earlySingletonObjects源码分析

/**
 * 二级缓存具体的创建逻辑
 */
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
	// 赋值当前的Bean
	Object exposedObject = bean;
	// 遍历所有的BeanPostProcessors,去执行初始化后(AOP)的逻辑
	if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
		for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
			exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
		}
	}
	return exposedObject;
}

/**
 * 具体的没一个初始化后(AOP)处理
 */
public Object getEarlyBeanReference(Object bean, String beanName) {
	// 得到Bean的名称
	Object cacheKey = getCacheKey(bean.getClass(), beanName);
	// 放入earlyProxyReferences:记录某个原始对象是否进行过AOP了。
	this.earlyProxyReferences.put(cacheKey, bean);
	// 有必要的时候生成代理对象
	return wrapIfNecessary(bean, beanName, cacheKey);
}

/**
 * 有必要的时候生成代理对象
 */
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
	// 当前Bean被代理过,直接返回
	if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
		return bean;
	}

	// advisedBeans表示已经判断过了的bean,false表示此bean不需要进行Aop
	if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
		return bean;
	}

	// 当前正在创建的Bean不用进行AOP,比如切面Bean
	if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

	// Create proxy if we have advice.
	// 判断当前bean是否存在匹配的advice,如果存在则要生成一个代理对象
	// 此处根据类以及类中的方法去匹配到Interceptor(也就是Advice),然后生成代理对象,代理对象在执行的时候,还会根据当前执行的方法去匹配
	Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
	if (specificInterceptors != DO_NOT_PROXY) {
		// advisedBeans记录了某个Bean已经进行过AOP了
		this.advisedBeans.put(cacheKey, Boolean.TRUE);
		// 进行AOP操作
		Object proxy = createProxy(
				bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
		// 一个代理对象的缓存
		this.proxyTypes.put(cacheKey, proxy.getClass());
		// 返回进过AOP的类
		return proxy;
	}

	// 没有进行过AOP
	this.advisedBeans.put(cacheKey, Boolean.FALSE);
	// 返回之前的Bean
	return bean;
}

三级缓存singletonFactories源码分析

// 循环依赖-添加到三级缓存,在创建Bean的doCreateBean方法中
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
	......
	// 如果不支持循环依赖,可以通过设置applicationContext.setAllowCircularReferences(false)进行配置不支持循环依赖
	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));
	}
	......
}

/**
 * 添加三级缓存的具体实现
 */
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
	Assert.notNull(singletonFactory, "Singleton factory must not be null");
	// 防止并发
	synchronized (this.singletonObjects) {
		// 一级缓存不存在的时候进入的判断里面
		if (!this.singletonObjects.containsKey(beanName)) {
			// 三级缓存把lambda表达式放入进去
			this.singletonFactories.put(beanName, singletonFactory);
			// 删除二级缓存
			this.earlySingletonObjects.remove(beanName);
			// 记录当前这个Bean正在被创建
			this.registeredSingletons.add(beanName);
		}
	}
}

解决循环依赖最核心的代码:一二级缓存找不到会去执行三级缓存的lambda表达式,并放入二级缓存!

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	// Quick check for existing instance without full singleton lock
	// 单例池存在Bean
	Object singletonObject = this.singletonObjects.get(beanName);
	// 单例池不存在,并且正在创建,去二级缓存中找
	if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
		singletonObject = this.earlySingletonObjects.get(beanName);
		// 二级缓存中不存在并且第二个参数为true(默认为true,放入二级缓存)
		if (singletonObject == null && allowEarlyReference) {
			synchronized (this.singletonObjects) {
				// Consistent creation of early reference within full singleton lock
				singletonObject = this.singletonObjects.get(beanName);
				if (singletonObject == null) {
					singletonObject = this.earlySingletonObjects.get(beanName);
					if (singletonObject == null) {
						// 从三级缓存中得到当前Bean对应的信息
						ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
						// 存在这些信息的时候
						if (singletonFactory != null) {
							// 执行三级缓存的lambda表达式,并且赋值给外部的结果
							singletonObject = singletonFactory.getObject();
							// 将执行结果放入二级缓存
							this.earlySingletonObjects.put(beanName, singletonObject);
							// 删除当前数据在三级缓存总的存储值
							this.singletonFactories.remove(beanName);
						}
					}
				}
			}
		}
	}
	// 返回从单例池,或者早期单例池,或者刚执行完lambda表达式的结果
	return singletonObject;
}

进行过循环依赖,实际初始化后(AOP)拿到的值是平台对象,需要从二级缓存中获取代理对象

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {
	......
	// 出现了循环依赖
	if (earlySingletonExposure) {
		// 从二级缓存中拿到提前进行AOP的代理对象
		Object earlySingletonReference = getSingleton(beanName, false);
		// 从二级缓存中拿到了代理对象
		if (earlySingletonReference != null) {
			// 实际的Bean与初始化后执行完拿到的代理对象一致,赋值从二级缓存拿到的代理对象。
			// 这里简单理解就是提前进行了AOP,初始化后直接返回了当前的对象
			if (exposedObject == bean) {
				exposedObject = earlySingletonReference;
			}
			// 不一致的情况,被其他自定义的AOP切面改动了,比如异步@Async也会生成一个代理对象。这时候就会有俩个代理对象!
			else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
				// beanName被哪些bean依赖了,现在发现beanName所对应的bean对象发生了改变,那么则会报错
				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 " +
							"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
				}
			}
		}
	}
	......
}

结束语

  • 获取更多本文的前置知识文章,以及新的有价值的文章,让我们一起成为架构师!
  • 关注公众号,可以让你对MySQL、并发编程、spring源码有深入的了解!
  • 关注公众号,后续持续高效的学习JVM!
  • 这个公众号,无广告!!!每日更新!!!
    作者公众号.jpg
posted @ 2022-02-23 00:00  程序java圈  阅读(347)  评论(0编辑  收藏  举报