Spring之循环依赖底层源码(一)

文章目录

    • 一、简介
      • 1. 回顾
      • 2. 循环依赖
      • 3. Bean的生命周期回顾
      • 4. 三级缓存
      • 5. 解决循环依赖的思路
    • 二、源码分析
    • 三、相关问题
      • 1. @Async情况下的循环依赖解析
      • 2. 原型Bean情况下的循环依赖解析
      • 3. 构造方法导致的循环依赖解析

一、简介

1. 回顾

前面首先重点分析了Spring Bean的整个生命周期的源码,同时还分析了Spring底层依赖注入的原理,依赖注入中的一个关键问题,即循环依赖问题在本部分详细分析

2. 循环依赖

首先什么是循环依赖?很简单,就是A对象依赖了B对象,B对象依赖了A对象。
在这里插入图片描述

如果不考虑Spring,循环依赖并不是问题,因为对象之间相互依赖是很正常的事情。但是,在Spring中循环依赖就是一个问题了,为什么? 因为,在Spring中,一个对象并不是简单new出来了,而是会经过一系列的Bean的生命周期,就是因为Bean的生命周期所以才会出现循环依赖问题。当然,在Spring中,出现循环依赖的场景很多,有的场景Spring自动帮我们解决了,而有的场景则需要程序员来解决,下文详细来说。

3. Bean的生命周期回顾

这里不会对Bean的生命周期进行详细的描述,只描述一下大概的过程。

  • Spring扫描class得到BeanDefinition
  • 根据得到的BeanDefinition去生成bean
  • 首先根据class推断构造方法
  • 根据推断出来的构造方法,反射,得到一个对象(暂时叫做原始对象)
  • 填充原始对象中的属性(依赖注入)
  • 如果原始对象中的某个方法被AOP了,那么则需要根据原始对象生成一个代理对象
  • 把最终生成的代理对象放入单例池(源码中叫做singletonObjects)中,下次getBean时就直接从单例池拿即可

比如上文说的A类,A类中存在一个B类的b属性,所以,当A类生成了一个原始对象之后,就会去给b 属性去赋值,此时就会根据b属性的类型和属性名去BeanFactory中去获取B类所对应的单例bean。 如果此时BeanFactory中存在B对应的Bean,那么直接拿来赋值给b属性;如果此时BeanFactory中 不存在B对应的Bean,则需要生成一个B对应的Bean,然后赋值给b属性。问题就出现在第二种情况,如果此时B类在BeanFactory中还没有生成对应的Bean,那么就需要去生成,就会经过B的Bean的生命周期。那么在创建B类的Bean的过程中,如果B类中存在一个A类的a属性,那么在创建B的Bean的过程中就 需要A类对应的Bean,但是,触发B类Bean的创建的条件是A类Bean在创建过程中的依赖注入,所以 这里就出现了循环依赖:

ABean创建–>依赖了B属性–>触发BBean创建—>B依赖了A属性—>需要ABean(但ABean还在创建过程中)

从而导致ABean创建不出来,BBean也创建不出来。这是循环依赖的场景,但是上文说了,在Spring中,通过某些机制帮开发者解决了部分循环依赖的问 题,这个机制就是三级缓存。

4. 三级缓存

三级缓存是通用的叫法。 一级缓存为:singletonObjects 二级缓存为:earlySingletonObjects 三级缓存为 **:singletonFactories**,先稍微解释一下这三个缓存的作用,后面详细分析:

  • singletonObjects中缓存的是已经经历了完整生命周期的bean对象。
  • earlySingletonObjects比singletonObjects多了一个early,表示缓存的是早期的bean对象。 早期是什么意思?表示Bean的生命周期还没走完就把这个Bean放入了earlySingletonObjects。
  • ingletonFactories中缓存的是ObjectFactory,表示对象工厂,表示用来创建早期bean对象的 工厂。

5. 解决循环依赖的思路

在这里插入图片描述
A的Bean在创建过程中,在进行依赖注入之前,先把A的原始Bean放入缓存(提早暴露,只要放到缓 存了,其他Bean需要时就可以从缓存中拿了),放入缓存后,再进行依赖注入,此时A的Bean依赖 了B的Bean,如果B的Bean不存在,则需要创建B的Bean,而创建B的Bean的过程和A一样,也是先创建一个B的原始对象,然后把B的原始对象提早暴露出来放入缓存中,然后在对B的原始对象进行依赖注入A,此时能从缓存中拿到A的原始对象(虽然是A的原始对象,还不是最终的Bean),B的原始对象依赖注入完了之后,B的生命周期结束,那么A的生命周期也能结束。因为整个过程中,都只有一个A原始对象,所以对于B而言,就算在属性注入时,注入的是A原始 象,也没有关系,因为A原始对象在后续的生命周期中在堆中没有发生变化。从上面这个分析过程中可以得出,只需要一个缓存就能解决循环依赖了,那么为什么Spring中还需要 singletonFactories呢?
这是难点,基于上面的场景想一个问题:如果A的原始对象注入给B的属性之后,A的原始对象进行了 AOP产生了一个代理对象,此时就会出现,对于A而言,它的Bean对象其实应该是AOP之后的代理对象,而B的a属性对应的并不是AOP之后的代理对象,这就产生了冲突。

AOP就是通过一个BeanPostProcessor来实现的,这个BeanPostProcessor就是 AnnotationAwareAspectJAutoProxyCreator,它的父类是AbstractAutoProxyCreator,而在 Spring中AOP利用的要么是JDK动态代理,要么CGLib的动态代理,所以如果给一个类中的某个方法 设置了切面,那么这个类最终就需要生成一个代理对象。

一般过程就是:A类—>生成一个普通对象–>属性注入–>基于切面生成一个代理对象–>把代理对 象放入singletonObjects单例池中。

而AOP可以说是Spring中除开IOC的另外一大功能,而循环依赖又是属于IOC范畴的,所以这两大功能想要并存,Spring需要特殊处理。如何处理的,就是利用了第三级缓存singletonFactories。

二、源码分析

首先我们回到doCreateBean源码:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)throws BeanCreationException {// 实例化bean// Instantiate the bean.BeanWrapper instanceWrapper = null;if (mbd.isSingleton()) {// 有可能在本Bean创建之前,就有其他Bean把当前Bean给创建出来了(比如依赖注入过程中)instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);}if (instanceWrapper == null) {// 创建Bean实例instanceWrapper = createBeanInstance(beanName, mbd, args);}Object bean = instanceWrapper.getWrappedInstance();Class<?> beanType = instanceWrapper.getWrappedClass();if (beanType != NullBean.class) {mbd.resolvedTargetType = beanType;}// 后置处理合并后的BeanDefinition// Allow post-processors to modify the merged bean definition.synchronized (mbd.postProcessingLock) {if (!mbd.postProcessed) {try {applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);}catch (Throwable ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Post-processing of merged bean definition failed", ex);}mbd.postProcessed = true;}}// 为了解决循环依赖提前缓存单例创建工厂// 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);}}if (earlySingletonExposure) {Object earlySingletonReference = getSingleton(beanName, false);if (earlySingletonReference != null) {if (exposedObject == bean) {exposedObject = earlySingletonReference;}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.");}}}}

上面源码中有关循环依赖最核心的源码是下面这段代码

// 为了解决循环依赖提前缓存单例创建工厂//当前创建的bean是否是单例&&当前spring是否支持循环依赖&&当前的bean是否正在创建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));}

上面代码在做了一系列判断后,将一个lamda表达式添加到第3级缓存中。虽然添加到了三级缓存中,但我们需要知道spring此时是不知道当前bean是否出现循环依赖的,此时它仅仅是没有目的存

第三级缓存就是ingletonFactories

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {Assert.notNull(singletonFactory, "Singleton factory must not be null");//这部分是线程安全的 synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {this.singletonFactories.put(beanName, singletonFactory);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}}

上面代码就完成了第三级缓存的填充,只有在属性真正注入的时候我们才需要开始着手去处理循环依赖问题。在属性注入的时候,我们需要通过getbean方法来获取我们需要的bean,我们进入getbean方法。

	@Overridepublic Object getBean(String name) throws BeansException {return doGetBean(name, null, null, false);}

然后getBean调用doGetBean方法。

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)throws BeansException {// name有可能是 &xxx 或者 xxx,如果name是&xxx,那么beanName就是xxx// name有可能传入进来的是别名,那么beanName就是idString beanName = transformedBeanName(name);Object beanInstance;// Eagerly check singleton cache for manually registered singletons.Object sharedInstance = getSingleton(beanName);if (sharedInstance != null && args == null) {if (logger.isTraceEnabled()) {if (isSingletonCurrentlyInCreation(beanName)) {logger.trace("Returning eagerly cached instance of singleton bean '" + beanName +"' that is not fully initialized yet - a consequence of a circular reference");}else {logger.trace("Returning cached instance of singleton bean '" + beanName + "'");}}// 如果sharedInstance是FactoryBean,那么就调用getObject()返回对象beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);}else {// Fail if we're already creating this bean instance:// We're assumably within a circular reference.if (isPrototypeCurrentlyInCreation(beanName)) {throw new BeanCurrentlyInCreationException(beanName);}// Check if bean definition exists in this factory.BeanFactory parentBeanFactory = getParentBeanFactory();if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {// Not found -> check parent.// &&&&xxx---->&xxxString nameToLookup = originalBeanName(name);if (parentBeanFactory instanceof AbstractBeanFactory) {return ((AbstractBeanFactory) parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);}else if (args != null) {// Delegation to parent with explicit args.return (T) parentBeanFactory.getBean(nameToLookup, args);}else if (requiredType != null) {// No args -> delegate to standard getBean method.return parentBeanFactory.getBean(nameToLookup, requiredType);}else {return (T) parentBeanFactory.getBean(nameToLookup);}}if (!typeCheckOnly) {markBeanAsCreated(beanName);}StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate").tag("beanName", name);try {if (requiredType != null) {beanCreation.tag("beanType", requiredType::toString);}RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);// 检查BeanDefinition是不是Abstract的checkMergedBeanDefinition(mbd, beanName, args);// Guarantee initialization of beans that the current bean depends on.String[] dependsOn = mbd.getDependsOn();if (dependsOn != null) {// dependsOn表示当前beanName所依赖的,当前Bean创建之前dependsOn所依赖的Bean必须已经创建好了for (String dep : dependsOn) {// beanName是不是被dep依赖了,如果是则出现了循环依赖if (isDependent(beanName, dep)) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");}// dep被beanName依赖了,存入dependentBeanMap中,dep为key,beanName为valueregisterDependentBean(dep, beanName);// 创建所依赖的beantry {getBean(dep);}catch (NoSuchBeanDefinitionException ex) {throw new BeanCreationException(mbd.getResourceDescription(), beanName,"'" + beanName + "' depends on missing bean '" + dep + "'", ex);}}}// Create bean instance.if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args);}catch (BeansException ex) {// Explicitly remove instance from singleton cache: It might have been put there// eagerly by the creation process, to allow for circular reference resolution.// Also remove any beans that received a temporary reference to the bean.destroySingleton(beanName);throw ex;}});beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}else if (mbd.isPrototype()) {// It's a prototype -> create a new instance.Object prototypeInstance = null;try {beforePrototypeCreation(beanName);prototypeInstance = createBean(beanName, mbd, args);}finally {afterPrototypeCreation(beanName);}beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);}else {String scopeName = mbd.getScope();if (!StringUtils.hasLength(scopeName)) {throw new IllegalStateException("No scope name defined for bean ´" + beanName + "'");}Scope scope = this.scopes.get(scopeName);if (scope == null) {throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");}try {  // session.getAttriute(beaName)  setAttriObject scopedInstance = scope.get(beanName, () -> {beforePrototypeCreation(beanName);try {return createBean(beanName, mbd, args);}finally {afterPrototypeCreation(beanName);}});beanInstance = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);}catch (IllegalStateException ex) {throw new ScopeNotActiveException(beanName, scopeName, ex);}}}catch (BeansException ex) {beanCreation.tag("exception", ex.getClass().toString());beanCreation.tag("message", String.valueOf(ex.getMessage()));cleanupAfterBeanCreationFailure(beanName);throw ex;}finally {beanCreation.end();}}// 检查通过name所获得到的beanInstance的类型是否是requiredTypereturn adaptBeanInstance(name, beanInstance, requiredType);}

Object sharedInstance = getSingleton(beanName);尝试从一级缓存singletonObjects,获取所要注入的bean对象

@Nullableprotected Object getSingleton(String beanName, boolean allowEarlyReference) {// Quick check for existing instance without full singleton lockObject singletonObject = this.singletonObjects.get(beanName);//如果没有从单例if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {synchronized (this.singletonObjects) {// Consistent creation of early reference within full singleton locksingletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}}}return singletonObject;}

如果我们顺利从单例池中找到了我们需要的bean,就可以直接返回完成属性注入了。如果没有找到,它会调用isSingletonCurrentlyInCreation(beanName)判断当前的bean是否正在创建中

public boolean isSingletonCurrentlyInCreation(String beanName) {return this.singletonsCurrentlyInCreation.contains(beanName);
}
private final Set<String> singletonsCurrentlyInCreation =Collections.newSetFromMap(new ConcurrentHashMap<>(16));

singletonsCurrentlyInCreation是一个集合,用来存储当前正在创建的bean。

由于bean的创建在一个线程中是串行的,所以我们依赖的那个bean可能还没创建,所以会返回false。所以上面函数直接返回空,回到doGetBean方法。现在就需要创建这个被依赖的bean了。而创建这bean的时候,依赖注入的时候,发现这个bean(这里假设为Bservice)也有依赖的对象,同样要进行上面的逻辑,进入doGetBean方法,查找这个依赖所依赖的bean。if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) ,此时这个if判断就可以成功进入,因为依赖当前bean的之前的那个bean(这里假设为Aservice)也在创建中。然后执行if后面的代码。首先

singletonObject = this.earlySingletonObjects.get(beanName);

上面代码从二级缓存this.earlySingletonObjects中拿,此时是拿不到的,根据前面的分析我们只是会在创建AService时将有关Aservice的一个lamda表达式没有目的的存在了三级缓存中(所有的bean在创建的时候都会有这个动作)。所以上面代码会返回一个空。

if (singletonObject == null && allowEarlyReference) {

如果拿到了空,就判断你是否支持循环依赖,默认是支持的,所以if判断成功继续执行后面的代码(此时说明出现了循环依赖,这里就判断了循环依赖)。

synchronized (this.singletonObjects) {//此时有可能创建好了,再次从一级缓存(单例池中拿)singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null) {//如果一级缓存中没有拿到,再从二级缓存中拿singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null) {//二级缓存中没有拿到,从三级缓存中拿ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {//执行lamda表达式singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}}

由于我们这个是循环依赖场景,所以一定会执行这句ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);代码,从三级缓存中拿。这里就拿到了前面的lamda表达式。singletonObject = singletonFactory.getObject();这句就执行了lamda表达式。生成了一个半成品的AService,然后将其放入二级缓存汇总,同时从三级缓存中移除当前的lamda表达式。上面就是解决循环依赖的关键代码。那么lamda表达式到底做了什么事,我们回到前面的代码中。

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

发现lamda表达式中执行了一个getEarlyBeanReference方法。

	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject = bean;//遍历beanpostprocessif (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);}}return exposedObject;}

Object exposedObject = bean;就是我们需要提前暴露的bean。然后执行 bp.getEarlyBeanReference(exposedObject, beanName);

//循环依赖提前进行AOP的方法
@Overridepublic Object getEarlyBeanReference(Object bean, String beanName) {Object cacheKey = getCacheKey(bean.getClass(), beanName);this.earlyProxyReferences.put(cacheKey, bean);return wrapIfNecessary(bean, beanName, cacheKey);}
//初始化后正常进行AOP的方法@Overridepublic Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);if (this.earlyProxyReferences.remove(cacheKey) != bean) {return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;}

这个方法是AbstractAutoProxyCreator类中的,该类是AOP的关键类。而getEarlyBeanReference就是用来提前进行AOP的方法。this.earlyProxyReferences.put(cacheKey, bean);这个map用来记录当前这个bean提前进行了AOP,wrapIfNecessary(bean, beanName, cacheKey);该方法用来判断当前bean是否需要进行AOP,如果需要就进行AOP。这部分后面分析AOP的时候会详细进行分析。

为什么要提前进行AOP,我们知道前面我们是将一个Lamda表达式存入了三级缓存中,lamda表达式需要调用一个getEarlyBeanReference(beanName, mbd, bean)方法,bean参数就是当前正在创建的Bean的一个半成品,通过遍历BeanPostProcessors来判断当前类是不是有AOP,如果没有我们会直接返回一个当前bean得半成品对象,然后后面会将这个bean对象存入二级缓存中,用于依赖注入。如果有AOP,它这个lamda表达式就会提前进行AOP,返回一个当前正在创建Bean的AOP对象,那么这种机制就解决了一个关键问题,因为AOP通常是在初始化之后,由于循环依赖的存在,我们需要将未创建好的对象提前进行注入,如果我们注入的是bean原始的半成品对象,而其实我们需要的是一个AOP对象,那么当前bean创建完成后就会发现注入的对象不一致的问题—即注入的不是最终的AOP代理对象。所以这里要提前进行AOP,在Bservice创建的过程中,提前对AService进行AOP生成其代理对象,然后注入到BService属性中完成BService的创建,最后AService注入已经创建好的BService对象,这样就成功解决了循环依赖问题。

当循环依赖的背景下,我们提前进行了AOP,到初始化后,当我们需要真正进行AOP的时候,就会调用postProcessAfterInitialization方法。

@Overridepublic Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);if (this.earlyProxyReferences.remove(cacheKey) != bean) {return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;}

该方法首先判断this.earlyProxyReferences.remove(cacheKey) != bean,判断我们是否已经进行了AOP,如果已经进行了AOP就会直接返回原始对象,否则开始真正的AOP。

this.earlyProxyReferences这个map就存储了提前进行了AOP的代理对象

继续回到doCreateBean方法。前面通过 exposedObject = initializeBean(beanName, exposedObject, mbd);进行了bean的初始化的动作,根据postProcessAfterInitialization我们知道,如果我们没有进行提前AOP的话,它会执行真正的AOP返回一个代理对象,如果进行了提前AOP,它会返回一个原始bean,但我们知道我们压根就不需要原始的bean,那Spring是怎么对这个原始bean进行处理,最后向单例池中存入我们需要的AOP的代理对象的,接着看后面代码

if (earlySingletonExposure) {Object earlySingletonReference = getSingleton(beanName, false);if (earlySingletonReference != null) {if (exposedObject == bean) {exposedObject = earlySingletonReference;}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.");}}}}

首先调用Object earlySingletonReference = getSingleton(beanName, false);,再次从单例池中拿对象。我们回到getSingleton方法的逻辑,首先它会从一级缓存中拿对象,现在肯定是拿不到的,然后从二级缓存中拿,现在是肯定拿的到,而且拿到的是通过前面lamda表达式生成的AOP提前代理对象(AService的提前AOP对象)。

然后我们接着执行

if (exposedObject == bean) {exposedObject = earlySingletonReference;}

它会对我们从二级缓存中拿到的对象与前面真正AOP的过程中返回的对象进行比较,我们这里这种情况,exposedObject返回的是一个原始的bean,而bean代表是原始bean。所以这里肯定是相等的。然后将exposedObject设置为二级缓存中的提前AOP代理对象。最后doCreateBean就会返回这个提前AOP对象。到此AService就创建完了,然后注入到BService的属性中,BService也可以成功创建完,最后AService将创建好的BService也注入到自己的属性中,这样循环依赖也解决了。下图总结了大致流程:
在这里插入图片描述

三、相关问题

1. @Async情况下的循环依赖解析

在Spring中,使用 @Async 注解时,可能会遇到循环依赖的问题。@Async 注解导致问题的底层原因涉及到Spring框架中对异步方法的处理方式。当使用 @Async 注解时,Spring会在运行时创建一个代理,该代理负责异步执行被注解的方法。这涉及到创建一个新的线程或使用线程池来执行方法,并且可能导致异步方法的执行时机不同于同步方法。下面用案例演示一下这个情况:

  1. 创建AService
@Component
public class AService {@AutowiredBService bService;@Asyncpublic void asyncMethod() {// 在异步方法中使用myOtherService...bService.doSomething();}
}

AService中的asyncMethod方法上面加了@Async注解,spring底层后创建一个AService的代理对象,然后实现异步调用

  1. 创建BService
@Component
public class BService {@AutowiredAService bService;public void doSomething() {System.out.println("BService");}
}
  1. 给两个Service加入AOP
@Aspect
@Component
public class MyAspect {@Before("execution(* com.zhouyu.service.AService.*(..))")public void beforeAServiceMethodExecution() {System.out.println("Before executing a method in AService.");}@Before("execution(* com.zhouyu.service.BService.*(..))")public void beforeBServiceMethodExecution() {System.out.println("Before executing a method in BService.");}
}
  1. 配置类开启AOP支持
@ComponentScan("com.zhouyu")
@EnableScheduling
@PropertySource("classpath:spring.properties")
@EnableAspectJAutoProxy
public class AppConfig {}
  1. 编写测试类

public class Test {public static void main(String[] args) {AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);// 获取 BoxService beanAService aService = (AService) applicationContext.getBean("AService");// 关闭 ApplicationContextapplicationContext.close();}
}

在这里插入图片描述
发现报错类,为什么会报错我们分析一下。其实当我们加入@Async注解后,在AOP之后会在原来的7个BeanPostProcessor的基础上多一个AsynAnnotationBeanPostProcessor,而且它在我们的AOP之后(AnnotationAwareAspectJAutoProxy这个生成AOP代理对象的BeanPostProcessor之后),这个会生成一个新的代理对象(不是AOP在循环依赖情况下返回的原始bean,而是生成一个新的代理对象),用于异步执行被@Async注释的方法。

在这里插入图片描述
然后这就会改变我们我们原来生成的AOP对象,这样当我们执行下面代码执行if (exposedObject == bean) {这个exposedObject和bean不一样,而执行else if后面的代码,然后抛出异常。

if (earlySingletonExposure) {Object earlySingletonReference = getSingleton(beanName, false);if (earlySingletonReference != null) {if (exposedObject == bean) {exposedObject = earlySingletonReference;}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.");}}}}

那么怎么解决这个问题,思路是加上@Lazy注解

在这里插入图片描述
然后就不报错了。但为什么lazy会解决这个问题,我们知道Lazy是延迟加载,在创建AService由于需要BService对象进行注入,当AService来创建BService时看到它有Lazy注解,所以会直接返回BService的一个代理对象注入AService,这样就不会有下面的流程,所以就解决了上面的问题(因为循环依赖压根就没有产生)。
在这里插入图片描述

2. 原型Bean情况下的循环依赖解析

我们给AService和BService都变成原型Bean

@Component
@Scope("prototype")
public class AService {@AutowiredBService bService;public void asyncMethod() {// 在异步方法中使用myOtherService...bService.doSomething();}
}@Component
@Scope("prototype")
public class BService {@AutowiredAService bService;public void doSomething() {System.out.println("BService");}
}

那么这种方式可以解决循环依赖问题吗,答案是不能,下面我们分析一下。首先需要创建AService,创建AService的时候发现需要BService进行注入,那么就开始创建BService,而创建BService的时候又需要创建AService,而AService是一个原型bean,所以会创建一个新的AService,所以我们用不了三级缓存中创建好的临时对象,然后AService又创建BService此时就不断重复上面过程,解决上面问题的方法就是将其中一个Bean设置为单例的就行。

3. 构造方法导致的循环依赖解析

其实构造方法导致的循环依赖的情况和使用Autowire是一样的

@Component
public class AService {BService bService;	public AService(BService bservice){this.bService=bservice;}public void asyncMethod() {// 在异步方法中使用myOtherService...bService.doSomething();}
}@Component
public class BService {AService aService;public BService(AService aservice){this.aService=aservice;}public void doSomething() {System.out.println("BService");}
}

在创建AService过程中,由于只有一个构造函数,Spring就会调用这个构造函数来创建AService,而发现构造函数的参数需要一个BService,所以去创建一个BService,而BService创建又需要AService,此时会直接报错,这点和@Autowired不同,因为@Autowired底层实际通过调用默认构造函数已经创建了一个临时对象,而构造函数这种类型的循环依赖由于构造函数都没有执行完,所以这个临时对象都没有生成,所以直接报错,解决方法同样是Lazy注解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/603575.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

力扣刷题记录(29)LeetCode:695、1020、130

695. 岛屿的最大面积 这道题和计算岛屿周长类似&#xff0c;在这里dfs的功能就是由一块陆地出发&#xff0c;找出这块陆地所在的岛屿并返回岛屿面积。 class Solution { public:int dfs(vector<vector<int>>& grid,int i,int j){if(i<0||i>grid.size())…

JavaScript获取后端json数据创建表格

怎么在前端获取后端数据生成表格json $.ajax({url: /Resource/GetResource,data: { searchText: searchText },success: function (response) {/*searchResult.innerHTML response;*/console.log(输入框 &#xff1a;, response);// 假设你有一个具有 id 为 "tableContai…

表格封装之 useForm 封装

在日常开发中&#xff0c;后端管理系统中增删改查的开发大多是重复机械式的工作&#xff0c;为了减少解放自己的双手&#xff0c;我们可以对这部分工作的内容进行封装。 一般的增删改查页面&#xff0c;由顶部搜索区&#xff0c;中部表格区&#xff0c;底部功能区&#xff08;…

Unity 面试篇|(二)Unity基础篇 【全面总结 | 持续更新】

目录 1.Unity3d脚本从唤醒到销毁有着一套比较完整的生命周期&#xff0c;列出系统自带的几个重要的方法。2.Unity3D中的碰撞器和触发器的区别&#xff1f;3.物体发生碰撞的必要条件&#xff1f;4.简述Unity3D支持的作为脚本的语言的名称&#xff1f;5. .Net与Mono的关系&#x…

镜头选型和计算

3.5 补充知识 一、单像元分辨率&#xff08;单像素精度&#xff09; 单像素精度是表示视觉系统综合精度的指标&#xff0c;表示一个像元对应检测目标的实际物理尺寸&#xff0c;是客户重点关注的 视觉系统参数&#xff1b; 计算公式1&#xff1a;单像素精度视野范围FOV/相机分辨…

Unity 点击对话系统(含Demo)

点击对话系统 可实现点击物体后自动移动到物体附近&#xff0c;然后弹出对话框进行对话。 基于Unity 简单角色对话UI脚本的编写&#xff08;新版UI组件&#xff09;和Unity 关于点击不同物品移动并触发不同事件的结合体&#xff0c;有兴趣可以看一下之前文章。 下边代码为U…

【数据库原理】(11)SQL数据查询功能

基本格式 SELECT [ALL|DISTINCT]<目标列表达式>[,目标列表达式>]... FROM <表名或视图名>[,<表名或视图名>] ... [ WHERE <条件表达式>] [GROUP BY<列名 1>[HAVING <条件表达式>]] [ORDER BY <列名 2>[ASC DESC]];SELECT: 指定要…

QT上位机开发(文本编辑器的界面开发)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 文本编辑器是编程开发中经常使用到的一个软件&#xff0c;比如说notepad就是其中一种。这里说编写一个文本编辑器&#xff0c;并不是说真的要写一个…

Linux 部署 AI 换脸

我使用的系统是 Ubuntu 20.04 文章实操主要分为以下几个部分 1、python 环境安装 2、下载 FaceFusion 上传服务器 3、创建 python 虚拟环境 4、下载 FaceFusion 依赖&#xff08;这里的命令执行时间会很长&#xff0c;够你睡午觉了&#xff09; 5、运行 FaceFusion 6、开…

python基础—网络编程

网络基本协议 TCP协议 UDP协议 二者对比&#xff1a; 连接性&#xff1a; TCP是面向连接的协议&#xff0c;需要在传输数据之前先进行三次握手建立连接。而UDP是无连接的协议&#xff0c;可以直接发送数据&#xff0c;无需事先建立连接。 可靠性&#xff1a; TCP提供了数…

js数组元素的排序

JavaScript 中的数组可以使用多种方法进行排序。下面是一些常见的排序方法&#xff1a; sort() 方法 sort() 方法用于对数组的元素进行排序。默认情况下&#xff0c;sort() 方法将数组元素转换为字符串&#xff0c;然后按照字符的 Unicode 码点进行排序。这可能导致一些不符合…

Golang拼接字符串性能对比

g o l a n g golang golang的 s t r i n g string string类型是不可修改的&#xff0c;对于拼接字符串来说&#xff0c;本质上还是创建一个新的对象将数据放进去。主要有以下几种拼接方式 拼接方式介绍 1.使用 s t r i n g string string自带的运算符 ans ans s2. 使用…

如何将手机中termux用电脑的vnc显示

在电脑中我们同样需要下载 vnc 这里填写手机上的 IP&#xff1a;端口号 我的是 10.11.166.219:5902 下面填名字然后 手机端 输入sshd开始ssh这边就可以连接啦

java spring mvc 初探 web搭建过程详解

提前准备安装tomcat 设备&#xff1a;mac 第一步&#xff1a;下载 进入官网下载压缩包 注意&#xff1a;如果jdk版本是1.8&#xff0c;则tomcat需要v8才行&#xff0c;否则会报错 https://tomcat.apache.org/ 第二步&#xff1a;解压 解压后路径 /Users/you/Library/tomcat…

使用PyTorch实现去噪扩散模型

在深入研究去噪扩散概率模型(DDPM)如何工作的细节之前&#xff0c;让我们先看看生成式人工智能的一些发展&#xff0c;也就是DDPM的一些基础研究。 VAE VAE 采用了编码器、概率潜在空间和解码器。在训练过程中&#xff0c;编码器预测每个图像的均值和方差。然后从高斯分布中对…

CAN协议

文章目录 CAN介绍CAN的优势多主控制通信速度较快&#xff0c;通信距离远具有错误检测、错误通知和错误恢复功能故障封闭功能连接节点多 ISO11519-2物理层特性ISO11898物理层特性CAN 收发芯片 JTA1050 CAN 协议5 种帧5种帧介绍数据帧的构成帧起始仲裁段控制段数据段CRC段ACK段帧…

一文讲透使用Python绘制双纵轴线图

双纵轴线图主要用来展示两个因变量和一个自变量的关系&#xff0c;并且两个因变量的数值单位不同。具体来说&#xff0c;双纵轴线图是指在一幅图上有一个横轴和两个纵轴&#xff0c;适用于三个变量。两个纵轴分别表示一个变量&#xff0c;横轴变量同时适用于两个纵轴上的变量&a…

报错curl: (6) Could not resolve host: raw.githubusercontent...的解决办法

我起初想要在macOS系统安装pip包&#xff0c;首先在终端安装homebrew&#xff0c;敲了命令&#xff1a;/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent...)" 之后触发的报错&#xff0c;报错内容&#xff1a;curl: (6) Could not resolve host: raw.…

【大数据】Flink CDC 的概览和使用

Flink CDC 的概览和使用 1.什么是 CDC2.什么是 Flink CDC3.Flink CDC 前生今世3.1 Flink CDC 1.x3.2 Flink CDC 2.x3.3 Flink CDC 3.x 4.Flink CDC 使用5.Debezium 标准 CDC Event 格式详解 1.什么是 CDC CDC&#xff08;Change Data Capture&#xff0c;数据变更抓取&#xf…

专业级的渗透测试服务,助力航空业数字化安全启航

​某知名航空公司是中国首批民营航空公司之一&#xff0c;运营国内外航线200多条&#xff0c;也是国内民航最高客座率的航空公司之一。在数字化发展中&#xff0c;该航空公司以数据驱动决策&#xff0c;通过精细化管理、数字创新和模式优化等方式&#xff0c;实现了精准营销和个…