Spring 循环依赖解决方案

文章目录

  • 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 {@AutowiredPerson person;
}package org.example.bean;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Component
public class Person {@Autowiredprivate 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<?> singleAssert.notNull(beanName, "Bean name must not be null");synchronized(this.singletonObjects) {Object singletonObject = this.singletonObjects.get(beanNameif (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);}//catchreturn 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 lockObject 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.rhkb.cn/news/400437.html

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

相关文章

Java并发类的主要API方法-CountDownLatch和CyclicBarrier

1.概念介绍 CountDownLatch 是一个计数器&#xff0c;计数器的初始值由创建它时指定。每次调用 countDown() 方法时&#xff0c;计数器会减1&#xff0c;直到计数器值变为0时&#xff0c;所有调用 await() 的线程都会被唤醒继续执行。 CyclicBarrier 是 Java 中另一个常用的同…

渗透率超50%,新能源汽车已成中国制造新名片

近日&#xff0c;乘联会公布了最新一期全国乘用车市场分析报告。报告显示&#xff0c;今年7月&#xff0c;中国乘用车市场零售销量173.2万辆&#xff0c;批发销量195.6万辆&#xff1b;同期&#xff0c;新能源乘用车零售销量87.8万辆&#xff0c;批发销量94.5万辆。按此计算&am…

DC-4靶机渗透测试

一、靶机下载地址 https://www.vulnhub.com/entry/dc-4,313/ 二、信息收集 1、主机发现 # 使用命令 nmap 192.168.145.0/24 -sn | grep -B 2 "00:0C:29:43:49:A5" 2、端口扫描 # 使用命令 nmap 192.168.145.217 -p- -sV 3、指纹识别 # 使用命令 whatweb "…

顺序表各种接口的实现(C)

线性表 线性表是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构常见的线性表&#xff1a;顺序表、链表、栈、队列、字符串…线性表在逻辑上是线性结构&#xff0c;也就说是连续的一条直线。在物理结构上并不一定是连续的&#xff0c;线性表在物…

旋转字符串 | LeetCode-796 | 模拟 | KMP | 字符串匹配

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 &#x1f579;️KMP算法练习题 LeetCode链接&#xff1a;796. 旋转字符串 文章目录 1.题目描述&#x1f351;2.题解&#x1fad0;2.1 暴力解法&#x1fad2;2.2 模拟…

c# 排序、强转枚举

List<Tuple<double,int>> mm中doble从小到大排序 mm本身排序 在C#中&#xff0c;如果你有一个List<Tuple<double, int>>类型的集合mm&#xff0c;并且你想要根据Tuple中的double值&#xff08;即第一个元素&#xff09;从小到大进行排序&#xff0c;同…

[Qt][对话框][下]详细讲解

目录 1.Qt内置对话框0.有哪些1.消息对话框 QMessageBox2.颜色对话框 QColorDialog3.⽂件对话框 QFileDialog4.字体对话框 QFontDialog5.输⼊对话框 QInputDialog6.进度条对话框 QProgressDialog 1.Qt内置对话框 0.有哪些 Qt提供了多种可复⽤的对话框类型&#xff0c;即Qt标准…

【启明智显技术分享】工业级HMI芯片Model3C/Model3A开发过程中问题记录笔记二

一、Model3C/Model3A芯片介绍 Model3C/Model3A是启明智显针对工业、行业以及车载产品市场推出的一款高性能、低成本的工业级HMI&#xff08;Human-Machine Interface&#xff0c;人机界面&#xff09;芯片。两颗芯片硬件PIN TO PIN&#xff1b;区别在于内置的PSRAM大小不同。该…

百度地图动态绘制台风轨迹

效果图如下: 台风测试数据获取 关键代码: /*** 动态绘制点和线*/drawMakerByAnimate () {const pointsMap = typhoneData.points;const title = typhoneData.tfid + typhoneData.name;if (!pointsMap || pointsMap.length === 0) {return;}if (this.markers.length > 0 &…

Godot《躲避小兵》实战之设置项目

通过之前的学习我们已经基本了解了godot的界面&#xff0c;知道如何创建项目以及节点。那么&#xff0c;从这一章节我们将进入godot官方给我们提供的一个2D游戏开发的小教程进行入手&#xff0c;这个游戏并不是我自己的作品&#xff0c;而是我通过学习完之后&#xff0c;对其进…

C#如何将自己封装的nuget包引入到项目中

问题 自己封装好了一个nuget包&#xff0c;但是不想上传到外网&#xff0c;想局域网使用&#xff0c;有两种方案 搭建私有nuget仓库放到离线文件夹中直接使用 第一种方式请请参考proget安装 下面主要是第二种方式 准备 新建类库项目 using System;namespace ClassLibrary…

数据结构--图(Graph)

定义 图&#xff08;Graph&#xff09;是由顶点的有穷非空集合和顶点之间边的集合组成的一种非线性表结构&#xff0c;通常表示为&#xff1a;G(V,E)&#xff0c;其中&#xff0c;G表示一个图&#xff0c;V是图G中顶点的集合&#xff0c;E是图G中边的集合。 顶点&#xff08;…

阿里云智能大数据演进

本文根据7月24日飞天发布时刻产品发布会、7月5日DataFunCon2024北京站&#xff1a;大数据大模型.双核时代实录整理而成&#xff0c;演讲信息如下&#xff1a; 演讲人&#xff1a;徐晟 阿里云研究员/计算平台产品负责人 主要内容&#xff1a; Overview - 阿里云大数据 AI 产品…

经典算法题总结:数组常用技巧(双指针,二分查找和位运算)篇

双指针 在处理数组和链表相关问题时&#xff0c;双指针技巧是经常用到的&#xff0c;双指针技巧主要分为两类&#xff1a;左右指针和快慢指针。所谓左右指针&#xff0c;就是两个指针相向而行或者相背而行&#xff1b;而所谓快慢指针&#xff0c;就是两个指针同向而行&#xf…

【YOLO5 项目实战】(3)PCB 缺陷检测

欢迎关注『youcans动手学模型』系列 本专栏内容和资源同步到 GitHub/youcans 【YOLO5 项目实战】(1)YOLO5 环境配置与测试 【YOLO5 项目实战】(2)使用自己的数据集训练目标检测模型 【YOLO5 项目实战】(3)PCB 缺陷检测 【YOLO5 项目实战】(3)PCB 缺陷检测 1. PCB 缺陷检…

vue-cli 中 配置 productionSourceMap 为 false 失效?

背景 最近 发现 vuecli 构建的 项目中配置的 productionSourceMap 为 false 后 &#xff0c;生产代码 还是能够看到 sourceMap 文件 。 原因 生效前提条件 得设置 NODE_ENV 为 production 才会生效&#xff01; 解决 直接修改生产环境的配置 NODE_ENV 为 production 直接覆…

融资3亿美元——月之暗面:AI大模型领域的新星

月之暗面&#xff0c;这个名字在AI领域引起了不小的震动。作为国内大模型创业企业的佼佼者&#xff0c;月之暗面以其独特的技术优势和商业模式&#xff0c;迅速在激烈的市场竞争中崭露头角。同时以其出色的长文本处理能力和创新的商业模式吸引了众多投资者的目光。 优牛企讯-企…

基于DETR模型实现交通标志检测

交通标志检测在自动驾驶和交通监控中是一个重要的问题。目标检测技术极大地促进了这一过程的自动化。本文实现基于DETR目标检测模型识别交通标志。 Tiny LISA交通标志检测数据集 本文数据集使用Kaggle上提供的Tiny LISA交通标志检测数据集(https://www.kaggle.com/datasets/mm…

手把手教你CNVD漏洞挖掘 + 资产收集

0x1 前言 挖掘CNVD漏洞有时候其实比一般的edusrc还好挖&#xff0c;但是一般要挖证书的话&#xff0c;还是需要花时间的&#xff0c;其中信息收集&#xff0c;公司资产确定等操作需要花费一定时间的。下面就记录下我之前跟一个师傅学习的一个垂直越权成功的CNVD漏洞通杀&#…

MySql 5.7.1 分区的实践

在性能优化中&#xff0c;Mysql 进行分区&#xff0c;能有效提高查询效率&#xff0c;因此开始百度了起来。但是结果总不是那么一番风顺的。如今使用 uuid 作为主键的情况已是主流&#xff0c;因此在给表增加分区时&#xff0c;出现了如下错误&#xff1a; 错误&#xff1a; A …