Spring 循环依赖解决方案

news2024/9/21 4:22:30

文章目录

  • 1. 循环依赖的产生
  • 2. 循环依赖的解决模型
  • 3. 基于setter/@Autowired 的循环依赖
    • 1_编写测试代码
    • 2_初始化 Cat
    • 3_初始化 Person
    • 4_ 回到 Cat 的创建流程
    • 5_小结
  • 4. 基于构造方法的循环依赖
  • 5. 基于原型 Bean 的循环依赖
  • 6. 引人AOP的额外设计
  • 7. 总结

IOC 容器初始化bean对象的逻辑中可能会遇到bean 对象之间循环依赖的问题。Spring Framework 不提倡在实际开发中设计 bean 对象之间的循环依赖,但是当循环依赖的场景出现时IOC容器内部可以恰当地予以解决。

1. 循环依赖的产生

循环依赖,简单理解就是两个或多个bean 对象之间互相引用(互相持有对方的引用)。以下假定一个场景,人(Person)与猫(Cat)之间相互引用,人养猫,猫依赖人,用 UML 类图可以抽象为图所示。

在这里插入图片描述

循环依赖的产生通常与具体业务场景有关,例如在电商系统中,用户服务和订单服务之间就会存在循环依赖:用户服务需要依赖订单服务查询指定用户的订单列表,订单服务需要根据用户的详细信息对商品订单分类处理。Spring Framework 会针对不同类型的循环依赖实行不同的处理策略,下面逐个展开讲解。

2. 循环依赖的解决模型

IOC容器内部对于解决循环依赖主要使用了三级缓存的设计,其中的核心成员如下。

  • singletonObjects:一级缓存,存放完全初始化好的 bean 对象容器,从这个集合中提取出来的 bean 对象可以立即返回。
  • earlySingletonObjects:二级缓存,存放创建好但没有初始化属性的 bean 对象的容器,它用来解决循环依赖。
  • singletonFactories:三级缓存,存放单实例 Bean工厂的容器。
  • singletonsCurrentlyInCreation:存放正在被创建的 bean 对象名称的容器。
/** Cache of singleton objects: bean name –> bean instance */
private final Map singletonObjects = new ConcurrentHashMap(256);
/** Cache of singleton factories: bean name –> ObjectFactory */
private final Map> singletonFactories = new HashMap>(16);
/** Cache of early singleton objects: bean name –> bean instance */
private final Map earlySingletonObjects = new HashMap(16);
/**Names of beans that are currently in creation.**/
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap(16));

上述成员均在 DefaultListableBeanFactory 的父类 DefaultSingletonBeanRegistry 中。DefaultSingletonBeanRegistry 本身是一个单实例 bean 对象的管理容器,DefaultListableBeanFactory 继承它之后可以直接获得单实例 bean 对象的管理能力而无须重复设计。

3. 基于setter/@Autowired 的循环依赖

Spring Framework 可以解决的循环依赖类型是基于setter方法或 @Autowired 注解实现属性注入的循环依赖,整个 bean 对象的创建阶段和初始化阶段是分开的,这给了 IOC 容器插手处理的时机。下面通过一个具体的示例来讲解循环依赖的处理思路。

Spring单例对象的初始化大略分为三步:

  1. createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象;
  2. populateBean:填充属性,这一步主要是多bean的依赖属性进行填充;
  3. initializeBean:调用spring xml中的init 方法。

从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一步、第二步。也就是构造器循环依赖和field循环依赖。

1_编写测试代码

package org.example.bean;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Cat {
    @Autowired
    Person person;
}

package org.example.bean;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Person {
    @Autowired
    private Cat cat;
}


package org.example;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("org/example/bean");      
    }
}

由于 AnnotationConfigApplicationContext在创建时传入了组件扫描的根包,底层会在扫描后自动刷新IOC容器,由此就可以触发 person 与 cat 对象的初始化动作。

2_初始化 Cat

由于在字母表中 cat 比 person 的首字母靠前,IOC 容器会先初始化 cat 对象。

1、getSingleton 中的处理,方法中的关键步骤

在 bean 的创建流程中, doGetBean 和 createBean 方法(都在 AbstractBeanFactory 里实现)之间有一个特殊的步骤 beforeSingletonCreation,如代码所示(DefaultSingletonBeanRegistry.java):

private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);

public Object getSingleton(String beanName, ObjectFactory<?> single
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized(this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName
        if (singletonObject == null) {
            //....
            //【控制循环依赖的关键步骤】
            this.beforeSingletonCreation(beanName);
          	//......
            try {
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            } //catch finally ....
            //	......

beforeSingletonCreation 方法非常关键,它会检查当前正在获取的 bean 对象是否存在于 singletonsCurrentlyInCreation 集合中。如果当前 bean 对象对应的名称在该集合中已经存在,说明出现了循环依赖(同一个对象在一个创建流程中创建了两次),则抛出 BeanCurrentlyInCreationException 异常如代码所示。

protected void beforeSingletonCreation(String beanName) {
	if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
		throw new BeanCurrentlyInCreationException(beanName);
	}
}

2、对象创建完毕后的流程

第一次进人deGetBean方法汇总,此时 cat 对象对应的名称不在 singletonsCurrentlyInCreation 集合中,可以顺利进人createBean→doCreateBean 方法中,doCreateBean方法又分为3个步骤,其中第一个步骤 createBeanInstance 方法执行尝毕后,一个空的 cat 对象已经被成功创建。此时这个 cat 对象被称为“早期Bean”,而且被 BeanWrapper 包装。

接下来进入 populateBean 方法之前,源码中又设计了一个逻辑,该逻辑会提前暴露当前正在创建的 bean 对象引用,如代码所示(earySingletonExposure 的设计)。位置:AbstractAutowireCapableBeanFactory 中。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
			throws BeanCreationException {

		//.......
		
		// 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) {
			addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
		}

		// Initialize the bean instance.
		//......
}

注意源码中的 earlySingletonExposure 变量,它的值需要由三部分判断结果共同计算产生,包括:

  • 当前创建的 bean 对象是一个单实例 bean 对象:
  • IOC容器本身允许出现循环依赖(默认为true,在 Spring Boot 2.6.0 之后默认为 false);
  • 正在创建的单实例 bean 对象名称中存在当前 bean 对象的名称。

前两个条件的判断结果在当前测试场景中显然可知为 true,而第三个判断条件中,由于在上一个环节中看到 singletonsCurrentlyInCreation 集合中已经放人了当前正在创建的 “cat” 名称,因此第三个条件的判断结果也为 true。三个条件的判断结果全部为 true,所以会执行 if 结构中的 addSingletonFactory 方法。代码如下:

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)) {
			this.singletonFactories.put(beanName, singletonFactory);
			this.earlySingletonObjects.remove(beanName);
			this.registeredSingletons.add(beanName);
		}
	}
}

请注意,addSingletonFactory 方法的第二个参数是一个 ObjectFactory,在方法调用时以 Lambda 表达式传人。而方法的内部实现逻辑是将当前正在创建的 bean 对象名称保存到三级缓存 singletonFactories 中,并从二级缓存 earlySingletonObjects 中移除。此处由于二级缓存中没有正在创建的"cat"名称,因此当前环节可以简单理解为仅将cat对象的名称"cat"放人了三级缓存 singletonFactories 中。

这段代码发生在 createBeanInstance之后,populateBean()之前,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,此时将这个对象提前曝光出来,让大家使用。

3、依赖注入时的处理

下一个处理的时机是在 cat 对象的依赖注人时。由于使用@Autowired注解注入了Person对象,AutowiredAnnotationBeanPostProcessor 会在 postProcessProperties 方法回调时将 person 对象提取出并注入 cat 对象中。而注人的底层逻辑依然是使用 BearFactory 的 getBean 方法,如代码所示。

被依赖对象的底层获取逻辑:

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
	InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
	try {
		metadata.inject(bean, beanName, pvs);
	}//catch
	return pvs;
}

@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
	Field field = (Field) this.member;
	Object value;
	//从BeanFactory中获取被依赖对象
	//【此处源码有调整】 value = resolveFieldValue(field, bean, beanName);
	value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);

	//....
	if (value != null) {
		//反射注入属性
		ReflectionUtils.makeAccessible(field);
		field.set(bean, value);
	}
}

public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
		//if-else........
		else {
			//【此处源码有调整】
			Object result =  doResolveDependency(descriptor, requestingBeanName, 
					autowiredBeanNames, typeConverter);
			return result;
		}
	}

public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
	//try......
	if (instanceCandidate instanceof Class) {
				instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
			}
	//...
}
public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)
		throws BeansException {
	return beanFactory.getBean(beanName);
}

当执行到最后一个方法 resolveCandidate 时,会触发 person 对象的初始化全流程。

3_初始化 Person

创建 person 对象的过程与创建 cat 类似,都是执行 getBean→doGetBean,其中包含getSingleton的处理,以及对象创建完毕后将 Person 对象包装为 ObjectFactory 后放人三级缓存 singletonFactories 中,最后到了依赖注入的环节。由于person中使用 @Autowired 注解注人了cat,因此 AutowiredAnnotationBeanPostProcessor 处理注人的逻辑与 addSingletonFactory代码中一样,从BeanFactory中获取 cat 对象。

1、再次获取 cat 对象的细节

再次获取 cat 对象时执行的方法依然是 getBean→doGetBean,但是在 doGetBean万法中有一个非常关键的环节:getSingleton。注意方法名与上面讲解的一致,但它是另一个重载方法,如下:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
	// Quick check for existing instance without full singleton lock
	Object singletonObject = this.singletonObjects.get(beanName);
	//检查当前获取的bean对象是否正在创建
	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 lock
				//检查二级缓存中是否包含当前正在创建的bean 对象
				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) {
							//将 bean 对象放入二级缓存,并从三级缓存中移除
							singletonObject = singletonFactory.getObject();
							this.earlySingletonObjects.put(beanName, singletonObject);
							this.singletonFactories.remove(beanName);
						}
					}
				}
			}
		}
	}
	return singletonObject;
}

仔细观察代码中的逻辑,IOC容器为了确保一个单实例 bean 对象不被多次创建在此处下了非常大的检查成本。

检查的范围如下:如果当前获取的 bean 对象正在创建,并且二级缓存 earlySingletonObjects中没有正在创建的 bean 对象以及三级缓存 singletonFactories 中存在正在创建的 bean 对象,说明当前获取的 bean 对象是一个没有完成依赖注入的不完全对象。即便当前 cat 是一个不完全对象,它也真实地存在于 IOC 容器中,不会影响 cat 与 person 对象之间的依赖注入(即属性成员的引用),所以 getSingleton 方法会在判断条件成立后,将当前正在获取的cat对象从三级缓存 singletonFactories中移除并放人二级缓存 earlySingletonObjects中,最后返回cat对象。

提示:注意一点,此处如果仅有一级缓存,也可以处理循环依赖,Spring Framework
在此处设计了两级缓存,是考虑到AOP的情况,AOP场景下第三级缓存singletonFactories 有特殊作用。

2、Person初始化完成后的处理

通过 getSingleton方法获取到 cat 对象后,doGetBean 方法的后续动作与循环依赖无关,此处不再提及。person 对象的 populateBean 方法执行完毕后,意味着 person 对象的属性赋值和依赖注人工作完成。后续的 initializeBean 方法中会对 person 对象进行初始化逻辑相关处理,该动作也与循环依赖无关,一并跳过。

当 doCreateBean → createBean 方法执行完毕后,回到 getSingleton 方法(最早的含有Map的代码示例)中,在方法的最后有两个关键动作,如下所示。

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
	//.......
				try {
					//此处的singletonractory.getobject ()即createBean 方法
					singletonObject = singletonFactory.getObject();
					newSingleton = true;
				}//catch......... 
				finally {
					//......
					afterSingletonCreation(beanName);
				}
				if (newSingleton) {
					addSingleton(beanName, singletonObject);
				}
			}
			return singletonObject;
		}
	}
protected void afterSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
			// throw ex....
			throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
		}
	}
protected void addSingleton(String beanName, Object singletonObject) {
	synchronized (this.singletonObjects) {
		this.singletonObjects.put(beanName, singletonObject);
		this.singletonFactories.remove(beanName);
		this.earlySingletonObjects.remove(beanName);
		this.registeredSingletons.add(beanName);
	}
}

由上述源码可以发现,afterSingletonCreation 方法的作用是将当前正在获取的 bean 对象名称从 singletonsCurrentlyInCreation 中移除(移除后即代表当前环节中该 bean对象未正在创建),而 addSingleton方法的作用则是将创建好的 bean 对象放入一级缓存 singletonObjects 中,且从二级缓存 earlySingletonObjects 和三级缓singletonFactories 中移除,并记录已经创建的单实例 bean 对象。至此,一个 person 对象的创建流程完全结束。

4_ 回到 Cat 的创建流程

person 对象创建完成后,回到 cat 对象的创建流程中,此时 cat 对象的依赖注入工作尚未完成,此处会将完全创建好的 person 对象进行依赖注入。请注意,该动作完成后,即代表Cat与 Person 循环依赖的场景处理完毕。

后续的动作与Person一致,最终会将 cat 对象放入一级缓存 singletonObjects,并从其他几个缓存集合中移除,从而完成 cat 对象的创建。

5_小结

基于 setter 方法或 @Autowired属性注人的循环依赖,IOC 容器的解决流程如下。

  1. 创建bean 对象之前,将该 bean 对象的名称放人“正在创建的 bean 对象”集合singletonsCurrentlyInCreation中。

  2. doCreateBean 方法中的 createBeanInstance 方法执行完毕后,会将当前 bean对象放入三级缓存中。注意此处放人的是经过封装后的 ObjectFactory对象,在该对象中有额外的处理逻辑。

  3. 对 bean 对象进行属性赋值和依赖注入时,会触发循环依赖的对象注入。

  4. 被循环依赖的对象创建时,会检查三级缓存中是否包含且二级缓存中不包含正在创建的、被循环依赖的对象。如果三级缓存中存在且二级缓存不存在,则会将三级缓存的 bean 对象移入二级缓存,并进行依赖注如。

  5. 被循环依赖的 bean 对象创建完毕后,会将该 bean 对象放入一级缓存,并从其他缓存中移除。

  6. 所有循环依赖的 bean 对象均注入完毕后,一个循环依赖的处理流程结束。

在这里插入图片描述

4. 基于构造方法的循环依赖

对于基于构造方法的循环依赖场景,IOC 容器无法给予合理的处理,只能抛出 BeanCurrentlyInCreationException 异常,原因是通过构造方法进行循环依赖,在构造方法没有执行完毕时,bean 对象尚未真正创建完毕并返回,此时若不加干预,会导致参与循环依赖的对象产生构造方法循环调用闭环,从而一直在轮流创建对象,直至内存溢出。IOC容器为了避免对象的无限创建,采用 singletonsCurrentlyInCreation 集合记录正在创建的 bean 对象名称,当一个 bean 对象名称出现两次时,IOC 容器会认为出现了不可解决的循环依赖从而抛出 BeanCurrentlyIncreationException 异常。下面通过一个例子简要分析,代码如下所示。

@Component
public class Cat {
    public Cat(Person person) {
        this.person = person;
    }

    Person person;
}

@Component
public class Person {
    public Person(Cat cat) {
        this.cat = cat;
    }

    private Cat cat;
}

通过代码清单中的两个循环依赖的类,可以推演以下步骤。

  1. IOC容器首先创建 cat 对象,由于调用 cat 的构造方法需要依赖 person 对象,从而引发 person 对象的创建。
  2. IOC容器创建 person对象,由于调用 person 的构造方法需要依赖 cat 对象,从而引发 cat 对象的创建。
  3. IOC 容器第二次创建 cat 对象,由于第一次创建 cat 对象时在 singletonsCurrentlyInCreation 集合中存放了"cat"的名称,因此当第二次创建cat 对象时,singletonsCurrentlyInCreation 集合中已存在"cat"名称,从而抛出 BeanCurrentlyInCreationException 异常,表示出现了不可解决的循环依赖。

5. 基于原型 Bean 的循环依赖

对于基于原型 Bean 之间循环依赖的场景,IOC 容器也无法合理解决,因为 IOC 容器不会对原型 Bean 进行缓存,只会像记录单实例 Bean 的创建时那样记录正在创建的 bean 对象名称。这种设计会导致即使原型 bean 对象已经实例化完毕,也无法通过有效手段将该 bean 对象的引用暴露,从而引发原型 bean 对象的无限创建。以下是一个原型 Bean 场景的推演,测试代码可以选择上面的代码,只需给两个类声明 @Scope("prototype")注解。

  1. IOC容器首先创建 cat 对象,之后进行 person 对象的依赖注入,由于 person 被定义为原型 Bean,触发 person 对象的创建。
  2. IOC容器创建 person 对象,之后进行cat 对象的依赖注入,由于 cat 对象也被定义为原型 Bean,触发 cat 对象的全新创建。
  3. IOC 容器再次创建 cat 对象,由于第一次创建 cat对象时 prototypesCurrentlyInCreation 原型 bean 对象名称集合(注意与singletonsCurrentlyInCreation 集合区分)中已经存放了"cat"名称,因此当第二次创建时,prototypesCurrentlyInCreation 集合中已存在"cat"名称,从而抛出 BeanCurrentlyIncreationException 异常,表示出现了不可解决的循环依赖。

在这里插入图片描述

6. 引人AOP的额外设计

对于在 3.3.1 节中提到的 getSingleton方法中将三级缓存中的 bean 对象放入二级缓存的动作,其实如果仔细观察会发现,三级缓存中存放的是被封装过的 ObjectFactory 对象,而二级缓存中存放的是真正的 bean 对象,为什么会有 ObjectFactory 到 bean 对象之间的过渡呢 ?这就是 Spring Framework设计的高深之处。Spring Framework的两大核心特性中,除 IOC 之外还有一个重要特性是 AOP,在 bean对象创建完成后,IOC 容器会指派BeanPostProcessor 对需要进行 AOP 增强的 bean 对象进行代理对象的创建。原始的目标对象和被 AOP 增强的代理对象本质上是两个完全不同的对象,IOC 容器为了确保 bean 对象中最终注入的是 AOP 增强后的代理对象而不是原始对象,会在 ObjectFactory 到 bean 对象的过渡期间进行额外的检查,该环节的检查会提前创建代理对象,并替换原始对象。经过此法处理后的 bean 对象就是一个被 AOP 增强后的代理对象,即便后续执行属性赋值和依赖注入,最终也是给内层的目标对象赋值和注入,而不会有任何副作用,但是从 IOC 容器的整体角度而言,IOC 容器内部的所有 bean 对象通过依赖注入后的属性成员都是正确的 bean 对象(此处的正确是指如果一个 bean 对象的确需要被 AOP 增强,则注人的是正确的代理对象而不是错误的原始对象 )。

下面从源码的角度简单了解引入 AOP 之后的额外逻辑触发。通过下列代码可以发现 getEarlyBeanReference 方法的实现是回调 IOC 容器中所有SmartInstantiationAwareBeanPostProcessorgetEarlyBeanReference 方法,该方法可以获得单实例 bean 对象的引用,也正是通过该方法 IOC容器可以有机会将一个普通 bean 对象转化为被 AOP 增强的代理对象。

代码 getEarlyBeanReference 方法的实现:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
	throws BeanCreationException {
	//.......
	// 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) {			//此处调用
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}
	//.........
}

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

所有实现 AOP 增强的后置处理器都继承自AbstractAutoProxyCreator,而它本身实现了 SmartInstantiationAwareBeanPostProcessor 接口,内部自然有 getEarlyBeanReference 方法的实现了。如下所示。

public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
    //必要时创建代理对象
    return this.wrapIfNecessary(bean, beanName, cacheKey);
}

由 AbstractAutoProxyCreator 实现的逻辑可以明显看出,如果当前正在创建的 bean 对象的确需要创建代理对象(即有必要),则会先行创建代理对象,并替换原始对象。由此就解释了为什么 IOC 容器解决循环依赖需要使用三级缓存而不是二级。

至此,IOC 容器解决循环依赖的方案全部讲解完毕,读者最好能自行编写一些实际的测试代码,配合 Debug 体会一遍,以加深印象。

7. 总结

Spring 的循环依赖存在三种情况:

  1. 构造器的循环依赖
  2. 单例模式下的 setter /Autowired循环依赖
  3. 非单例循环依赖

Spring 通过提前暴露单例 bean的机制来处理属性注入中的循环依赖。对于构造器注入、非单例循环依赖,Spring 无法解决循环依赖问题,需要开发者注意设计避免循环依赖的情况。

实现原理是采用三级缓存保存提前暴露的单例 bean,且用 Set集合记录正在创建中的 bean 对象来判断是否产生了循环依赖。

需要理解三级缓存中各自的作用即工作流程。

Spring 通过这一系列策略确保了在大多数情况下能够正确地解决循环依赖问题,保证了容器的稳定性和一致性。

另外,@Lazy采用延迟加载的方式也可以解决循环依赖的问题。但这些都是治标不治本,辜负了SpringBoot的一片苦心。

项目中存在 Bean 的循环依赖,其实是代码质量低下的表现。多数人寄希望于框架层来给擦屁股,造成了整个代码的设计越来越糟,最后用一些奇技淫巧来填补犯下的错误。

还好,SpringBoot 终于受不了这种滥用,默认把循环依赖给禁用了!

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

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

相关文章

如何对open62541.h/open62541.c的UA_Client进行状态(在线/掉线)监控

文章目录 1.背景2.解决方案3.异步连接4.注意事项4.1.线程问题4.2.UA_Client_run_iterate 1.背景 目前在利用open62541.h/open62541.c编写了一个与PLC进行OPCUA通讯的上位机程序。 上位机这边会定时对PLC的某个opcua变量进行写操作。但是假如PLC离线或者说拔掉网线&#xff0c;…

【多线程-从零开始-柒】单例模式,饿汉和懒汉模式

单例模式&#xff1a;是一种设计模式 设计模式&#xff0c;类似于“棋谱”&#xff0c;就是固定套路&#xff0c;针对一些特定的场景&#xff0c;给出一些比较好的解决方法只要按照设计模式来写代码&#xff0c;就可以保证代码不会太差&#xff0c;保证代码的下限 设计模式 设…

8月8日学习笔记 python基础

1.环境 python2&#xff0c; python3 yum list installed|grep python yum -y install python3 # 最新安装3.12可以使⽤源码安装&#xff0c;教程是在第⼀个星期pdf python3 --version 3.6.8 #进⼊到python的编辑状态 python3 # 如果直接输⼊python&#xff0c;也会进⼊到pyth…

MySQL基础练习题33-有趣的电影

目录 题目 准备数据 分析数据 总结 题目 找出所有影片描述为 非 boring (不无聊) 的并且 id 为奇数 的影片。 返回结果按 rating 降序排列。 准备数据 ## 创建库 create database db; use db;## 创建表 Create table If Not Exists cinema (id int, movie varchar(255),…

php根据截止时间计算剩余的时间,并且在剩余时间不足1天时仅显示小时数

//获取政策库文章public function getIndexZckList(){$fl_id = input(fl_id);if(empty(

C++:list类(迭代器类)

前言 list是链表的意思 它属于链表中的带头双向循环链表 建议先掌握数据结构中的链表 C数据结构&#xff1a;单链表-CSDN博客 C数据结构&#xff1a;双向链表&#xff08;带头循环&#xff09;_c带头双向循环链表-CSDN博客 数据结构 首先我们需要一个链表的节点 templa…

ThinkPHP5漏洞分析之代码执行

漏洞概要 本次漏洞存在于 ThinkPHP 的缓存类中。该类会将缓存数据通过序列化的方式&#xff0c;直接存储在 .php 文件中&#xff0c;攻击者通过精心构造的 payload &#xff0c;即可将 webshell 写入缓存文件。缓存文件的名字和目录均可预测出来&#xff0c;一旦缓存目录可访问…

【张】#12 enum 枚举

enum 枚举定义格式&#xff1a; enum <类型名> {<枚举常量表> }; 枚举其实就是一个整数 enum example {Aa,Bb10,Cc //给Bb赋值为10后&#xff0c;Cc的值会变成11 }; 枚举变量只能使用枚举值&#xff0c;枚举可以赋值给整型&#xff0c;整型不能赋值给枚举 #inc…

掌握Jenkins自动化部署:从代码提交到自动上线的全流程揭秘

Jenkins自动化部署是现代软件开发中不可或缺的一部分&#xff0c;它不仅简化了代码的发布过程&#xff0c;还为整个团队带来了无与伦比的效率和协作力。想象一下&#xff0c;开发者们可以专注于编写高质量的代码&#xff0c;而不是为繁琐的手动部署所烦恼&#xff1b;测试人员能…

力扣高频SQL 50题(基础版)第四十四题之626. 换座位

文章目录 力扣高频SQL 50题&#xff08;基础版&#xff09;第四十四题之626. 换座位626. 换座位题目说明思路分析实现过程准备数据实现方式结果截图 力扣高频SQL 50题&#xff08;基础版&#xff09;第四十四题之626. 换座位 626. 换座位 题目说明 表: Seat --------------…

<数据集>街头摊贩识别数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;966张 标注数量(xml文件个数)&#xff1a;966 标注数量(txt文件个数)&#xff1a;966 标注类别数&#xff1a;1 标注类别名称&#xff1a;[street-vendor] 序号类别名称图片数框数1street-vendor9662016 使用标注…

Java流程控制02:if选择结构

本节内容教学视频链接&#xff1a;Java流程控制04&#xff1a;if选择结构_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV12J41137hu?p36&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 在Java中&#xff0c;if 选择结构用于根据特定条件执行不同的代码块。 if语句有四…

CRC校验算法详解、C语言实现

一、前言 1.1 CRC算法介绍 CRC&#xff08;Cyclic Redundancy Check&#xff09;校验算法是一种广泛应用于数据通信和存储系统中的错误检测方法&#xff0c;主要用于检测数据在传输过程中是否发生了改变。CRC算法通过计算一个固定长度的校验码&#xff0c;将该校验码附加到原…

Zookeeper使用快速入门:基础命令,wacth监控,权限控制

目录 前置知识 1. 基础命令 未知指令&#xff1a; ls&#xff1a; create&#xff1a; zookeeper中节点有四种类型&#xff0c;分别是&#xff1a; 1. 持久节点&#xff08;Persistent Node&#xff09; 2. 临时节点&#xff08;Ephemeral Node&#xff09; 3. 持久顺序…

进程间通信 ---共享内存

序言 在前一篇文章中&#xff0c;我们介绍了名为 &#x1f449;管道 的进程间通信的方式&#xff0c;该种方式又可分为 匿名管带&#xff0c;命名管道。前者最大的特点就是 仅支持包含血缘关系两进程之间的通信&#xff0c;而后者 支持任意进程间的通信。  在本篇文章中&…

python3.9+wxPython设计的一个简单的计算器

运行环境&#xff1a;python3.9wxPython4.2.1 运行效果&#xff1a; 按下等于号&#xff0c;输出&#xff1a; 按下R键&#xff0c;保留两位小数 键盘布局与逻辑分离&#xff0c;添加删除功能一般功能或修改键盘布局只需要更改词典的顺序即可。添加特殊功能时则需要将队对应的…

【kubernetes】k8s配置资源管理

一、ConfigMap资源配置 ConfigMap保存的是不需要加密配置的信息 ConfigMap 功能在 Kubernetes1.2 版本中引入&#xff0c;许多应用程序会从配置文件、命令行参数或环境变量中读取配置信息。ConfigMap API 给我们提供了向容器中注入配置信息的机制&#xff0c;ConfigMap 可以被…

基于vue框架的CKD电子病历系统nfa2e(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;患者,医生,药品信息,电子病历,临时医嘱,长期医嘱,健康科普 开题报告内容 基于Vue框架的CKD电子病历系统 开题报告 一、选题背景 随着信息技术的飞速发展和医疗信息化的深入推进&#xff0c;电子病历系统&#xff08;Electronic Medic…

SpringBoot事务-调度-缓存

一.Spring Boot中的事务管理 设置事务 Transactional(isolation Isolation.DEFAULT) Transactional(propagation Propagation.REQUIRED) 开启事务 EnableTransactionManagement ​​​​​​​ 1. 开启事务管理 要开启 Spring 的事务管理&#xff0c;你需要在你的 Spring B…

Docker 日志管理

一、ELK -Filebeat Elasticsearch 数据的存储和检索 常用端口&#xff1a; 9100&#xff1a;elasticsearch-head提供web访问 9200&#xff1a;elasticsearch与其他程序连接或发送消息 9300&#xff1a;elasticsearch集群状态 Logstash 有三个组件构成input&#xff0c;fi…