万字长文带你吃透Spring是怎样解决循环依赖的

这篇具有很好参考价值的文章主要介绍了万字长文带你吃透Spring是怎样解决循环依赖的。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在Spring框架中,处理循环依赖一直是一个备受关注的话题。这是因为Spring源代码中为了解决循环依赖问题,进行了大量的处理和优化。同时,循环依赖也是Spring高级面试中的必考问题,回答得好可以成为面试中的必杀技。因此,本文旨在为大家提供深入了解Spring的循环依赖及其解决方案的资料,让读者能够在日后的面试中更有把握地回答相关问题!

一、什么是循环依赖

循环依赖其实就是循环引用,也就是一个或多个以上的对象互相持有对方,最终形成闭环,形成一个无限循环的依赖关系。比如 A依赖于A本身(左图),A依赖于B,B也依赖与A(中),A依赖B,B依赖C,C又依赖A(右图)。
万字长文带你吃透Spring是怎样解决循环依赖的
如果不考虑Spring,循环依赖并不是问题,因为对象之间相互依赖是很正常的事情,后面我们都以A和B的相互循环依赖进行举例:

	A a = new A();
	B b = new B();

	a.b = b;
	b.a = a;

然而,Spring的循环依赖通常被单独拎出来谈论,也经常在面试中被提及。这是因为 Spring 中对象的创建和管理是由 IOC 控制的,一个对象的创建不仅仅是简单地调用 new,而是经过了一系列 Bean 的生命周期。因此,循环依赖问题也就会随之而来。当然,Spring 中存在许多场景会导致循环依赖,有些场景 Spring 能够解决,而有些场景则需要程序员手动解决。

要深刻理解 Spring 中的循环依赖问题,首先需要理解 Spring 中 Bean 的生命周期。

二、Bean的生命周期

Spring Bean的生命周期来说,可以分为四个主要阶段:实例化、属性复制、初始化、销毁,其中经过初始化以后这个bean就被创建完成可以供使用了。

具体的步骤:

  • 实例化:实例化一个 Bean 对象
  • 属性赋值:为 Bean 设置相关属性和依赖注入
  • 初始化:初始化的阶段的步骤比较多,5和6 步是进行真正的初始化,而第 3和4 步为在初始化前执行,第 7 步在初始化后执行,如果原始对象中的某个方法被 AOP 了,那么这一步需要根据原始对象生成一个代理对象,最终生成的代理对象放入单例池,Bean 就可以被使用了。
  • 销毁:第 8~10 步,第 8 步其实也可以算到销毁阶段,但不是真正意义上的销毁,而是先在使用前注册了销毁的相关调用接口,为了后面第 9、10 步真正销毁 Bean 时再执行相应的方法
    万字长文带你吃透Spring是怎样解决循环依赖的

我们可以发现,在第2步中,Spring 需要给对象中的属性进行依赖注入,那么这个注入过程是怎样的?

还是以A和B两个类相互依赖来举例子,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 在创建过程中进行依赖注入,所以这里就出现了循环依赖
万字长文带你吃透Spring是怎样解决循环依赖的
在Spring中,是通过三级缓存来解决循环依赖问题的,那么什么是三级缓存?

三、三级缓存

Spring的三级缓存是指在使用Spring框架进行Bean的创建和管理时,Spring在其内部维护了三级缓存,用于提高Bean的创建和获取效率。这三级缓存分别是singletonObjects、earlySingletonObjects和singletonFactories:singletonObjects用于缓存完全初始化后的单例Bean实例,earlySingletonObjects用于缓存尚未完全初始化的单例Bean实例,而singletonFactories则用于缓存Bean工厂对象,即可以生成Bean实例的工厂方法。

/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

一级缓存

singletonObjects:key -> beanName,value -> 完整bean对象

一级缓存的是经历了完整的Bean生命周期的Bean。

二级缓存

earlySingletonObjects:key -> beanName,value -> 不完整(属性信息未填充完毕)bean对象

二级缓存用于存放未完成Bean生命周期的半成品Bean。在出现循环依赖时,这些Bean已经被提前放入二级缓存。如果需要进行AOP处理,则其代理对象也已经被放入缓存。

三级缓存

singletonFactories:key -> beanName,value -> bean工厂对象

缓存的是ObjectFactory , 也就是一个Lambda表达式 , 这就一个方法getObject,返回bean对象或bean代理对象,用于解决被代理增强的循环依赖
万字长文带你吃透Spring是怎样解决循环依赖的

四、循环依赖的解决(源码分析)

我们已经知道,问题的关键在于A在创建的时候需要将B注入到A中,而注入B需要先创建B,创建B的时候发现需要将A注入到B中,产生了先有鸡还有现有蛋的问题。Spring解决这个问题的思想就是在实例化过程中,提前办成品bean放入缓存,在依赖注入的时候允许将半成品进行注入:
实例化A -> a的半成品写入缓存 -> 属性注入B -> B还没有实例化,需要先进行实例化B -> 实例化B -> 没有A,但是有A的半成品 -> 注入A的半成品 -> 实例化B成功 -> 实例化A成功

上面就是核心思想,下面将会结合源码进行具体分析其中的原理。

万字长文带你吃透Spring是怎样解决循环依赖的

4.1 普通循环依赖

还是使用之前的例子来举例:

@Component
public class A {
    // A中注入了B
    @Autowired
    private B b;
}

@Component
public class B {
    // B中也注入了A
    @Autowired
    private A a;
}

A和B进行初始化的过程大致如下:

  1. 使用getBean(A.class),首先获取容器内的单例A(若beanA不存在,就会走A的创建流程,有的话会直接从缓存里面获取),显然初次获取beanA是不存在的,因此会去创建A
  2. 实例化bean A,并将实例化后的A放入到三级缓存中,此时beanA.b == null
  3. 对A的属性进行依赖注入:@Autowired依赖注入beanB(此时需要去容器内获取beanB),通过getBean(B)去容器内找B,如果能找到B的实例的话就可以直接注入。但此时B在容器内或者缓存内不存在,因此需要创建B的bean。
  4. 实例化bean B,并将实例化后的A放入到二级缓存中,此时beanB.A == null
  5. 对B属性注入:@Autowired依赖注入beanA(此时需要去容器内获取beanA),这时会调用getBean(A)去容器内找到beanA。一级缓存不存在A,在二级缓存中也不存在A,三级缓存中存在A的ObjectFactory,此时会调用getEarlyBeanReference()方法得到A的实例化后的结果,将这个半成品bean放入到二级缓存中,同时将三级缓存中A的ObjectFactory删除掉。
  6. 将二级缓存中的半成品A注入到B中,B完成后续的初始化过程,最终放入到一级缓存中。
  7. A也在缓存中可以拿到了B的bean,将B注入到A中。
  8. A和B都创建完成。

万字长文带你吃透Spring是怎样解决循环依赖的
到这里为止有两个问题需要讨论一下,包括我可能存在疑问:

在上面的第6步中,将一个没有经过初始化的A类型对象提前注入B中不会有问题吗?不应该注入一个完整的A吗?

这样做是不会出问题的,虽然给B注入的是一个还未初始化的A对象,也就是半成品A,但是在创建A的流程中一直使用的是注入到B中的A对象的引用,之后会根据这个引用对A进行初始化,通过这个引用最后获取到的还是成品的A,所以这是没有问题的。

为什么三级缓存中存放的是ObjectFactory而不是bean呢?直接将半成品的bean放入到缓存中不可以么?为什么要对此一举再使用三级缓存存放ObjectFactory呢?

这个其实涉及到了下一个要讨论的问题,主要是因为三级缓存实际上跟Spring中的AOP相关,我们继续往下看吧。

4.2 有AOP的循环依赖

到目前为止,发现似乎不需要三级缓存,直接使用二级缓存貌似也能解决问题:

  • 实例化A,将实例化后的半成品放入到二级缓存
  • 实例化B,从缓存中获取半成品A,完成依赖注入
  • 初始化B,得到B的bean
  • 将B注入A,完成A和B的初始化

还记得第二部分Bean的生命周期吗,在生命周期的初始化过程中,如果有AOP的话需要执行相应的方法。还是A和B两个类相互依赖的场景,但是A类中多了一层AOP,也就是A类的原始对象赋值给B时 , 进行了AOP , 那么A进行AOP之后,它的真实对象是代理对象 , 那么B中A的值是原始对象的值 ,那么就会产生B的A值和A的实际值不符的问题,Spring解决的办法就是使用三级缓存。如果我们使用了三级缓存,就可以实现对A的提前AOP,将B真实依赖的A注入到B中。

那改变Bean的生命周期可以么?先AOP再放入缓存中呢?

如果这么做了,就把AOP中创建代理对象的时机提前了,不管是否发生循环依赖,都在doCreateBean方法中完成了AOP的代理。不仅没有必要,而且违背了Spring在结合AOP跟Bean的生命周期的设计。

五、哪种情况的循环可以解决

循环依赖问题在Spring中主要有三种情况:

  • 通过构造方法进行依赖注入时产生的循环依赖问题。
  • 通过setter方法进行依赖注入且是在多例模式下产生的循环依赖问题。
  • 通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。

在Spring中,只有第三种方式的循环依赖问题被解决了,其他两种方式在遇到循环依赖问题时都会产生异常。

多例模式不可以:只有单例bean才有支持循环依赖的可能,非单例的bean不支持循环依赖,会陷入死循环。

构造方法注入不可以:如果主bean对象通过构造函数方式注入所依赖的bean对象,则无论所依赖的bean对象通过何种方式注入主bean,都无法解决循环依赖问题,程序无法启动。主要原因是主bean对象通过构造函数注入所依赖bean对象时,无法创建该所依赖的bean对象,获取该所依赖bean对象的引用。

@Async导致无法支持循环依赖:@Async 标记的类是通过 AbstractAdvisingBeanPostProcessor 来生成代理的,AbstractAdvisingBeanPostProcessor 没有实现 SmartInstantiationAwareBeanPostProcessor

六、源码分析

现在进入源码分析部分。下面按照方法的调用顺序,依次来看一下循环依赖相关的代码。

获取单例Bean的源码

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
	...
	@Override
	@Nullable
	public Object getSingleton(String beanName) {
		return getSingleton(beanName, true);
	}
	@Nullable
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {
		Object singletonObject = this.singletonObjects.get(beanName);
		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				singletonObject = this.earlySingletonObjects.get(beanName);
				if (singletonObject == null && allowEarlyReference) {
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						singletonObject = singletonFactory.getObject();
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}
		return singletonObject;
	}
	...
	public boolean isSingletonCurrentlyInCreation(String beanName) {
		return this.singletonsCurrentlyInCreation.contains(beanName);
	}
	protected boolean isActuallyInCreation(String beanName) {
		return isSingletonCurrentlyInCreation(beanName);
	}
	...
}

doGetBean源码

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
	...
	protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
		...
		// Eagerly check singleton cache for manually registered singletons.
		// 先去获取一次,如果不为null,此处就会走缓存了
		Object sharedInstance = getSingleton(beanName);
		...
		// 如果不是只检查类型,那就标记这个Bean被创建了,添加到缓存里 也就是所谓的  当前创建Bean池
		if (!typeCheckOnly) {
			markBeanAsCreated(beanName);
		}
		...
		// Create bean instance.
		if (mbd.isSingleton()) {
		
			// 这个getSingleton方法不是SingletonBeanRegistry的接口方法  属于实现类DefaultSingletonBeanRegistry的一个public重载方法
			// 它的特点是在执行singletonFactory.getObject();前后会执行beforeSingletonCreation(beanName);和afterSingletonCreation(beanName);  
			// 也就是保证这个Bean在创建过程中,放入正在创建的缓存池里  可以看到它实际创建bean调用的是我们的createBean方法
			sharedInstance = getSingleton(beanName, () -> {
				try {
					return createBean(beanName, mbd, args);
				} catch (BeansException ex) {
					destroySingleton(beanName);
					throw ex;
				}
			});
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
		}
	}
	...
}

// 抽象方法createBean所在地  这个接口方法是属于抽象父类AbstractBeanFactory的   实现在这个抽象类里
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
	...
	protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
		...
		// 创建Bean对象,并且将对象包裹在BeanWrapper 中
		instanceWrapper = createBeanInstance(beanName, mbd, args);
		// 再从Wrapper中把Bean原始对象(非代理)  这个时候这个Bean就有地址值了,就能被引用了
		// 注意:此处是原始对象,这点非常的重要
		final Object bean = instanceWrapper.getWrappedInstance();
		...
		// earlySingletonExposure 用于表示是否”提前暴露“原始对象的引用,用于解决循环依赖。
		// 对于单例Bean,该变量一般为 true   但你也可以通过属性allowCircularReferences = false来关闭循环引用
		// isSingletonCurrentlyInCreation(beanName) 表示当前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");
			}
			// 上面讲过调用此方法放进一个ObjectFactory,二级缓存会对应删除的
			// getEarlyBeanReference的作用:调用SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference()这个方法  否则啥都不做
			// 也就是给调用者个机会,自己去实现暴露这个bean的应用的逻辑
			// 比如在getEarlyBeanReference()里可以实现AOP的逻辑  参考自动代理创建器AbstractAutoProxyCreator  实现了这个方法来创建代理对象
			// 若不需要执行AOP的逻辑,直接返回Bean
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}
		Object exposedObject = bean; //exposedObject 是最终返回的对象
		...
		// 填充属于,解决@Autowired依赖
		populateBean(beanName, mbd, instanceWrapper);
		// 执行初始化回调方法们
		exposedObject = initializeBean(beanName, exposedObject, mbd);
		
		// earlySingletonExposure:如果你的bean允许被早期暴露出去 也就是说可以被循环引用  那这里就会进行检查
		// 此段代码非常重要,但大多数人都忽略了它
		if (earlySingletonExposure) {
			// 此时一级缓存肯定还没数据,但是呢此时候二级缓存earlySingletonObjects也没数据
			//注意,注意:第二参数为false  表示不会再去三级缓存里查了

			// 此处非常巧妙的一点:因为上面各式各样的实例化、初始化的后置处理器都执行了,如果你在上面执行了这一句
			//  ((ConfigurableListableBeanFactory)this.beanFactory).registerSingleton(beanName, bean);
			// 那么此处得到的earlySingletonReference 的引用最终会是你手动放进去的Bean最终返回,完美的实现了"偷天换日" 特别适合中间件的设计
			// 我们知道,执行完此doCreateBean后执行addSingleton()  其实就是把自己再添加一次  ,再一次强调,完美实现偷天换日
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
			
				// 这个意思是如果经过了initializeBean()后,exposedObject还是木有变,那就可以大胆放心的返回了
				// initializeBean会调用后置处理器,这个时候可以生成一个代理对象,那这个时候它哥俩就不会相等了 走else去判断吧
				if (exposedObject == bean) {
					exposedObject = earlySingletonReference;
				} 

				// allowRawInjectionDespiteWrapping这个值默认是false
				// hasDependentBean:若它有依赖的bean 那就需要继续校验了(若没有依赖的 就放过它)
				else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
					// 拿到它所依赖的Bean们,下面会遍历一个一个的去看
					String[] dependentBeans = getDependentBeans(beanName);
					Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
					
					// 一个个检查它所以Bean
					// removeSingletonIfCreatedForTypeCheckOnly这个放见下面  在AbstractBeanFactory里面
					// 简单的说,它如果判断到该dependentBean并没有在创建中的了的情况下,那就把它从所有缓存中移除, 并且返回true
					// 否则(比如确实在创建中) 那就返回false 进入我们的if里面~  表示所谓的真正依赖
					//(解释:就是真的需要依赖它先实例化,才能实例化自己的依赖)
					for (String dependentBean : dependentBeans) {
						if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
							actualDependentBeans.add(dependentBean);
						}
					}

					// 若存在真正依赖,那就报错(不要等到内存移除你才报错,那是非常不友好的) 
					// 这个异常是BeanCurrentlyInCreationException,报错日志也稍微留意一下,方便定位错误
					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.");
					}
				}
			}
		}
		
		return exposedObject;
	}

	// 虽然是remove方法 但是它的返回值也非常重要
	// 该方法唯一调用的地方就是循环依赖的最后检查处
	protected boolean removeSingletonIfCreatedForTypeCheckOnly(String beanName) {
		// 如果这个bean不在创建中  比如是ForTypeCheckOnly的  那就移除掉
		if (!this.alreadyCreated.contains(beanName)) {
			removeSingleton(beanName);
			return true;
		}
		else {
			return false;
		}
	}

}

protected <T> T doGetBean(...){
	... 
	// 标记beanName a是已经创建过至少一次的,它会一直存留在缓存里不会被移除(除非抛出了异常)
	// 参见缓存Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256))
	if (!typeCheckOnly) {
		markBeanAsCreated(beanName);
	}

	// 此时a不存在任何一级缓存中,且不是在创建中  所以此处返回null
	// 此处若不为null,然后从缓存里拿就可以了(主要处理FactoryBean和BeanFactory情况吧)
	Object beanInstance = getSingleton(beanName, false);
	...
	// 这个getSingleton方法非常关键。
	//1、标注a正在创建中
	//2、调用singletonObject = singletonFactory.getObject();(实际上调用的是createBean()方法)  因此这一步最为关键
	//3、此时实例已经创建完成  会把a移除整整创建的缓存中
	//4、执行addSingleton()添加进去。(备注:注册bean的接口方法为registerSingleton,它依赖于addSingleton方法)
	sharedInstance = getSingleton(beanName, () -> { ... return createBean(beanName, mbd, args); });
}

getSingleton(beanName, singletonFactory)源码

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {

            // ....
            // 省略异常处理及日志
            // ....

            // 在单例对象创建前先做一个标记
            // 将beanName放入到singletonsCurrentlyInCreation这个集合中
            // 标志着这个单例Bean正在创建
            // 如果同一个单例Bean多次被创建,这里会抛出异常
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
            if (recordSuppressedExceptions) {
                this.suppressedExceptions = new LinkedHashSet<>();
            }
            try {
                // 上游传入的lambda在这里会被执行,调用createBean方法创建一个Bean后返回
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            }
            // ...
            // 省略catch异常处理
            // ...
            finally {
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = null;
                }
                // 创建完成后将对应的beanName从singletonsCurrentlyInCreation移除
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                // 添加到一级缓存singletonObjects中
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

doCreateBean源码

protected Object doCreateBean(){
    ...
    // 使用构造器/工厂方法   instanceWrapper是一个BeanWrapper
    instanceWrapper = createBeanInstance(beanName, mbd, args);
    // 此处bean为"原始Bean"   也就是这里的A实例对象:beanA@1
    final Object bean = instanceWrapper.getWrappedInstance();
    ...
    // 是否要提前暴露(允许循环依赖)  现在此处A是被允许的
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
    
    // 允许暴露,就把A绑定在ObjectFactory上,注册到三级缓存`singletonFactories`里面去保存着
    // Tips:这里后置处理器的getEarlyBeanReference方法会被促发,自动代理创建器在此处创建代理对象(注意执行时机 为执行三级缓存的时候)
    if (earlySingletonExposure) {
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    ...
    // exposedObject 为最终返回的对象,此处为原始对象bean也就是beanA@1,下面会有用处
    Object exposedObject = bean; 
    // 给A@1234属性完成赋值,@Autowired在此处起作用
    // 因此此处会调用getBean("b"),so 会重复上面步骤创建B类的实例
    // 此处我们假设B已经创建好了 为beanB@2
    
    // 需要注意的是在populateBean("b")的时候依赖有beanA,所以此时候调用getBean("a")最终会调用getSingleton("a"),
    //此时候上面说到的getEarlyBeanReference方法就会被执行。这也解释为何我们@Autowired是个代理对象,而不是普通对象的根本原因
    
    populateBean(beanName, mbd, instanceWrapper);
    // 实例化。这里会执行后置处理器BeanPostProcessor的两个方法
    // 此处注意:postProcessAfterInitialization()是有可能返回一个代理对象的,这样exposedObject 就不再是原始对象了  需要特别注意
    // 比如处理@Aysnc的AsyncAnnotationBeanPostProcessor它就是在这个时间里生成代理对象的(有坑,请小心使用@Aysnc)
    exposedObject = initializeBean(beanName, exposedObject, mbd);


    ... // 至此,相当于beanA@1已经实例化完成、初始化完成(属性也全部赋值了)
    // 这一步我把它理解为校验:校验:校验是否有循环引用问题


    if (earlySingletonExposure) {
        // 注意此处第二个参数传的false,表示不去三级缓存里singletonFactories再去调用一次getObject()方法了
        // 上面建讲到了由于B在初始化的时候,会触发A的ObjectFactory.getObject()  所以a此处已经在二级缓存earlySingletonObjects里了
        // 因此此处返回A的实例:beanA@1
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
        
            // 这个等式表示,exposedObject若没有再被代理过,这里就是相等的
            // 显然此处我们的a对象的exposedObject它是没有被代理过的  所以if会进去
            // 这种情况至此,就全部结束了
            if (exposedObject == bean) {
                exposedObject = earlySingletonReference;
            }
    
            // 继续以A为例,比如方法标注了@Aysnc注解,exposedObject此时候就是一个代理对象,因此就会进到这里来
            //hasDependentBean(beanName)是肯定为true,因为getDependentBeans(beanName)得到的是["b"]这个依赖
            else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                String[] dependentBeans = getDependentBeans(beanName);
                Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);


                // beanA@1依赖的是["b"],所以此处去检查b
                // 如果最终存在实际依赖的bean:actualDependentBeans不为空 那就抛出异常  证明循环引用了
                for (String dependentBean : dependentBeans) {
                    // 这个判断原则是:如果此时候b并还没有创建好,this.alreadyCreated.contains(beanName)=true表示此bean已经被创建过,就返回false
                    // 若该bean没有在alreadyCreated缓存里,就是说没被创建过(其实只有CreatedForTypeCheckOnly才会是此仓库)
                    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 " +
                            "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
                }
            }
        }
    }
}

七、常见面试题

7.1 Spring是如何解决循环依赖的

Spring使用了三级缓存来解决循环依赖。其中一级缓存存放完整bean对象,二级缓存存放半成品bean,三级缓存为对象工厂。

当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂并添加到三级缓存中。如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,通过这个工厂获取到的就是A实例化的对象。

当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去从缓存中获取,第一步,先获取到三级缓存中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束。

7.2 为什么需要三级缓存,二级缓存可以么?

不可以,主要为了解决AOP生产代理对象问题的。如果存在代理,三级没有问题,二级就不行了。因为三级缓存中放的是⽣成具体对象的匿名内部类,获取 Object 的时候,它可以⽣成代理对象,也可以返回普通对象。使⽤三级缓存主要是为了保证不管什么时候使⽤的都是⼀个对象。

如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则。

7.3 Spring能自动解决全部的循环依赖情况么?

  1. 只有单例bean才有支持循环依赖的可能,非单例的bean不会出现循环依赖。
  2. 如果存在循环依赖,且都是通过构造函数依赖的,这种情况下的循环依赖是无法解决的。

参考文献:

Spring 轻度解析之循环依赖源码解析
面试必杀技,讲一讲Spring中的循环依赖
Spring中的循环依赖
聊透Spring循环依赖文章来源地址https://www.toymoban.com/news/detail-417409.html

到了这里,关于万字长文带你吃透Spring是怎样解决循环依赖的的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 万字长文带你重温Elasticsearch ,这下完全懂了!

    生活中的数据 搜索引擎是对数据的检索,所以我们先从生活中的数据说起。我们生活中的数据总体分为两种: 结构化数据 非结构化数据 结构化数据: 也称作行数据,是由二维表结构来逻辑表达和实现的数据,严格地遵循数据格式与长度规范,主要通过关系型数据库进行存

    2024年02月22日
    浏览(47)
  • 【C++】一文带你吃透string的模拟实现 (万字详解)

    (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是 Scort 🎓 🌍博客主页:张小姐的猫~江湖背景 快上车🚘,握好方向盘跟我有一起打天下嘞! 送给自己的一句鸡汤🤔: 🔥真正的大师永远怀着一颗学徒的心 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏 🎉🎉欢迎持续关注! 🎨传统写

    2024年02月03日
    浏览(203)
  • 万字长文,带你彻底搞懂 HTTPS(文末附实战)

    大家好,我是满天星,欢迎来到我的技术角落,本期我将带你一起来了解 HTTPS。 PS:本文首发于微信公众号:技术角落。感兴趣的同学可以查看并关注:https://mp.weixin.qq.com/s/HbEhD93S7y3p8amlzS2sKw 其实网上写 HTTPS 的文章也不少了,但是不少文章都是从原理上泛泛而谈,只讲概念,

    2023年04月14日
    浏览(43)
  • 《万字长文带你解读AIGC》系列之技术篇

    欢迎关注『CVHub』官方微信公众号! Welcome to back! 在《万字长文带你解读AIGC入门篇》 一文中,我们详细为大家介绍了 AIGC 的相关概念、背景及其如此火爆的原因,接下来我们将进一步深入探讨AIGC背后的技术栈。 作为本系列的技术篇,将从多个角度来介绍 AIGC 的技术栈,其中

    2024年02月09日
    浏览(33)
  • 【CVHub】《万字长文带你解读AIGC》系列之入门篇

    本文来源“ CVHub ”公众号,侵权删,干货满满。 作者丨派派星 来源丨CVHub 原文链接:《万字长文带你解读AIGC》系列之入门篇 图0 随着 ChatGPT 的病毒式传播, 生成式人工智能 ( AIGC , a.k.a  AI-generated content )因其分析和创造 文本 、 图像 、 视频 以及其他方面的出众能力而俨

    2024年02月15日
    浏览(31)
  • spring 的循环依赖以及spring为什么要用三级缓存解决循环依赖

            bean的生命周期         这里简单过一下 class -无参构造 -普通对象 -依赖注入(对加了autowire等的属性赋值) -初始化前-初始化 -初始化后(aop) -放入单例池的map(一级缓存) -bean对象 这里提一点单例bean单例bean 其实就是用mapbeanName,Bean对象创建的,多例bean就不

    2024年02月15日
    浏览(53)
  • 万字长文带你走进MySql优化(系统层面优化、软件层面优化、SQL层面优化)

            MySQL 是一个关系型数据库管理系统,可以从不同的层面进行优化以提高系统的性能和效率。下面就是从 系统设计层面、软件层面、SQL层面的一些优化建议 。优化 MySql 可以从减轻数据库压力、提高配置、提高查询效率等方面入手。 采用分布式架构        如果单

    2024年01月22日
    浏览(49)
  • 【独家】万字长文带你梳理Llama开源家族:从Llama-1到Llama-3

     Datawhale干货  作者:张帆,陈安东,Datawhale成员 在AI领域,大模型的发展正以前所未有的速度推进技术的边界。 北京时间4月19日凌晨,Meta在官网上官宣了Llama-3,作为继Llama-1、Llama-2和Code-Llama之后的第三代模型,Llama-3在多个基准测试中实现了全面领先,性能优于业界同类最

    2024年04月25日
    浏览(33)
  • Spring解决循环依赖问题

    例如,就是A对象依赖了B对象,B对象依赖了A对象。(下面的代码属于 属性的循环依赖 ,也就是初始化阶段的循环依赖,区别与底下 构造器的循环依赖 ) 问题来了: A Bean创建 —— 依赖了 B 属性 ——  触发 B Bean创建 ——  B 依赖了 A 属性 ——  需要 A Bean(但A Bean还在创建

    2024年02月12日
    浏览(39)
  • Spring解决循环依赖

    目录 什么是spring循环依赖 什么情况下循环依赖可以被处理? spring 如何解决循环依赖 创建A这个Bean的流程 答疑 疑问:在给B注入的时候为什么要注入一个代理对象? 初始化的时候是对A对象本身进行初始化,而容器中以及注入到B中的都是代理对象,这样不会有问题吗? 三级

    2024年02月22日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包