当前位置: 首页 > news >正文

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 终于受不了这种滥用,默认把循环依赖给禁用了!

相关文章:

Spring 循环依赖解决方案

文章目录 1. 循环依赖的产生2. 循环依赖的解决模型3. 基于setter/Autowired 的循环依赖1_编写测试代码2_初始化 Cat3_初始化 Person4_ 回到 Cat 的创建流程5_小结 4. 基于构造方法的循环依赖5. 基于原型 Bean 的循环依赖6. 引人AOP的额外设计7. 总结 IOC 容器初始化bean对象的逻…...

可视化大屏:如何get到领导心目中的“科技感”?

你如果问领导可视化大屏需要什么风格的&#xff0c;领导大概率说科技感的&#xff0c;然后你就去做了&#xff0c;结果被劈了一顿&#xff0c;什么原因&#xff1f;因为你没有get到领导心目中描述的科技感。 一、为什么都喜欢科技感 科技感在可视化大屏设计中具有以下好处&am…...

基于Python的金融数据采集与分析的设计与实现

基于Python的金融数据采集与分析的设计与实现 “Design and Implementation of Financial Data Collection and Analysis based on Python” 完整下载链接:基于Python的金融数据采集与分析的设计与实现 文章目录 基于Python的金融数据采集与分析的设计与实现摘要第一章 绪论1…...

使用Sanic和SSE实现实时股票行情推送

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐&#xff1a;「storm…...

redis散列若干记录

字典 redis本身使用字典结构管理数据 redis使用hash表实现字典结构 使用了什么hash算法 使用SipHash算法&#xff0c;该算法能有效防止Hash表碰撞&#xff0c;并有不错的性能 hash冲突怎么解决 使用链表法解决hash冲突 hash表如何扩容 渐进式扩容&#xff0c;不会引起线程长期阻…...

Java面试八股之什么是STOMP协议

什么是STOMP协议 STOMP&#xff08;Simple Text Oriented Messaging Protocol&#xff09;是一种为消息队列和事件驱动架构设计的轻量级协议&#xff0c;主要用于在消息中间件之间进行消息交换。它的设计原则是简单、跨平台和易于实现&#xff0c;这使得STOMP成为许多实时应用…...

【自用】Python爬虫学习(一):爬虫基础与四个简单案例

Python爬虫学习&#xff08;一&#xff09; 基础知识四个简单的爬虫案列1.使用urlopen获取百度首页并保存2.获取某翻译单词翻译候选结果3.获取某网页中的书名与价格4.获取某瓣排名前250的电影名称 基础知识 对于一个网页&#xff0c;浏览器右键可以查看页面源代码&#xff0c;…...

[python]uiautomation.WindowControl函数用法

Python UIAutomation 窗口控件 介绍 在本文中&#xff0c;我们将探讨Python UIAutomation库以及如何使用它来控制和自动化Windows应用程序。我们将介绍UIAutomation的基础知识及其功能&#xff0c;并提供代码示例来演示其用法。 什么是UI自动化&#xff1f; UIAutomation是一个…...

学习记录第二十七天

进程 wait函数 功能 等待子进程结束&#xff1a;父进程调用wait函数后&#xff0c;会暂停执行&#xff0c;直到它的某个子进程结束。收集子进程状态&#xff1a;当子进程结束时&#xff0c;wait函数会返回子进程的终止状态&#xff0c;包括是正常终止还是被信号终止等信息。…...

servlet的执行顺序

执行的时候Tomcat先初始化 然后调用 server 根据server来回调请求方式下面会追入源码解释 package com.haogu.servlet;import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.…...

Go语言 类封装和绑定方法

本篇文章主要内容为Go语言类相关操作&#xff1a;封装和绑定方法介绍及示例。 目录 封装 绑定方法 类方法形参 指针形参 设置类方法参数 指针与非指针区别 总结 封装 go语言支持类的操作&#xff0c;但是没有class关键字&#xff0c;使用struct来模拟类。 示例如下&am…...

DirectShow过滤器开发-写WAV音频文件过滤器

下载本过滤器DLL 本过滤器将PCM音频流&#xff0c;或ADPCM&#xff0c;IEEE_FLOAT&#xff0c;ALAW&#xff0c;MULAW&#xff0c;GSM610音频流写入WAV音频文件。 写WAV音频文件过滤器信息 过滤器名称&#xff1a;写WAV 过滤器GUID&#xff1a;{CF704A9C-0C67-4712-BA33-DD0A…...

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

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

Docker最佳实践进阶(一):Dockerfile介绍使用

大家好,上一个系列我们使用docker安装了一系列的基础服务,但在实际开发过程中这样一个个的安装以及繁杂命令不仅仅浪费时间,更是容易遗忘,下面我们进行Docker的进阶教程,帮助我们更快速的部署和演示项目。 一、什么是Dockerfile? Dockerfile 是一个文本文件,其中包含了…...

Anything in Any Scene:无缝融入任何场景,实现逼真视频对象插入技术

人工智能咨询培训老师叶梓 转载标明出处 现实世界的视频捕获虽然因其真实性而宝贵&#xff0c;但常常受限于长尾分布的问题&#xff0c;即常见场景过度呈现&#xff0c;而关键的罕见场景却鲜有记录。这导致了所谓的"分布外问题"&#xff0c;在模拟复杂环境光线、几何…...

安卓开发中的AppCompat框架|安卓系统|安卓应用|兼容性|UI组件|核心组件|ActionBar|Fragment|最佳实践|框架|移动开发|移动应用

目录 1. 什么是AppCompat框架 1.1 AppCompat的起源 1.2 AppCompat的重要性 2. AppCompat框架的核心组件 2.1 AppCompatActivity 2.2 AppCompat主题 2.3 AppCompat Widgets 3. 在项目中使用AppCompat框架 3.1 添加依赖项 3.2 应用AppCompat主题 4. AppCompat的高级功…...

React使用useRef ts 报错

最近在写自己的React项目&#xff0c;我在使用useRef钩子函数的时候发现 TS2322: Type MutableRefObject<HTMLDivElement | undefined> is not assignable to type LegacyRef<HTMLDivElement> | undefined Type MutableRefObject<HTMLDivElement | undefined&g…...

python-信息交互-pyautogui

python-信息交互-pyautogui 一: pyautogui1> waht?2> 功能分类3> 概念及作用二: 通用功能1> function all2> function 注释三: 鼠标控制1> mouse functions2> mouse functions demo3> mouse drag demo四: keyboard控制1> keyboard functions2> …...

flink1.18 编译遇到的问题

1. flink-runtime-web编译失败 源码编译时一直卡在 [INFO] Running ‘npm ci --cache-max0 --no-save’ in 处理方法&#xff1a; 修改flink-runtime-web/pom.xml文件 将<arguments>ci --cache-max0 --no-save ${npm.proxy}</arguments> 替换为&#xff1a;<a…...

2024年8月份编译Openwrt系统基础

概述&#xff1a; 本文档记录openwrt系统的编译过程&#xff0c;以备后续再用&#xff0c;技术支持与指导&#xff01; 1.编译环境 环境需要Linux&#xff0c;我使用的环境是WSL2、Ubuntu 20.04 2.安装编译必须的依赖&#xff08;wsl、linux&#xff09; WSL2&#xff1a;Bu…...

Vue3+vite+ts 项目使用mockjs

1、安装mockjs npm i mockjs 2、安装vite-plugin-mock npm i vite-plugin-mock -D 3、安装axios npm i axios 4.在src目录下创建mock文件夹,在文件夹内创建login.ts等文件&#xff0c;并在文件夹内放置以下内容&#xff08;注&#xff1a;URL要和真实请求地址保持一致&am…...

动态规划(二)——例题

目录 Help Jimmy 题目 解题思路 神奇的口袋 题目 枚举的解法 递归的解法 动态规划的解法 滑雪 题目 解题思路 解法一 解法二 Help Jimmy 题目 "Help Jimmy" 是在下图所示的场景上完成的游戏&#xff1a; 场景中包括多个长度和高度各不相同的平台。地面是…...

Node.js中判断是文件还是文件夹的多种方法

在Node.js中&#xff0c;我们经常需要判断一个路径是文件还是文件夹。Node.js提供了多种方法来实现这一功能&#xff0c;本文将详细介绍这些方法&#xff0c;并给出相应的示例代码。 一、使用fs.Stats对象 在Node.js中&#xff0c;fs模块提供了fs.stat()或fs.statSync()方法&…...

idea 如何打war包

idea 如何打war包 1.在IntelliJ IDEA中打包WAR文件&#xff0c;你可以按照以下步骤操作:1.设置项目结构:首先&#xff0c;打开IDEA&#xff0c;选择File>Project Structure(或使用快捷键CtrlAltShiftS)。在打开的窗口中&#xff0c;选择 Artifacts 选项 2.添加Web Applicat…...

米联客-FPGA程序设计Verilog语法入门篇连载-15 Verilog语法_跨时钟域设计

软件版本&#xff1a;无 操作系统&#xff1a;WIN10 64bit 硬件平台&#xff1a;适用所有系列FPGA 板卡获取平台&#xff1a;https://milianke.tmall.com/ 登录“米联客”FPGA社区 http://www.uisrc.com 视频课程、答疑解惑&#xff01; 1概述 本小节主要讲解Verilog语法的…...

gradio 对话界面实现支持图片、视频正常显示

参考: https://www.gradio.app/docs/gradio/chatbot 问题: gradio网页输出视频nan;图片webp显示不出来 解决方法:需要通过gradio的Video、Image包装 代码: 这里下面启动个后端vlm模型(参考:https://blog.csdn.net/weixin_42357472/article/details/141126225),前端通…...

催收业务怎么提高接通率

提高催收呼叫业务的接通率是一个综合性的任务&#xff0c;需要从多个方面进行优化。以下是一些具体的策略和建议&#xff1a; 一、优化呼叫时间与频次 1. 选择合适的呼叫时间&#xff1a;通过分析目标客户的活跃时段&#xff0c;选择他们最可能接听电话的时间进行呼叫…...

动态生成sitemaps和robots.txt文件:提升SEO与网站可爬性

本文由 ChatMoney团队出品 在现代Web开发中&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;是网站成功的关键因素之一。搜索引擎通过网络爬虫来索引网页&#xff0c;而sitemaps和robots.txt文件则是帮助这些爬虫更好地理解和索引网站内容的重要工具。 sitemaps简介 Sit…...

LeetCode 第二十五天 2024.8.12

1. &#xff1a;递增子序列 题目链接: 491. 非递减子序列 - 力扣&#xff08;LeetCode&#xff09; 应用条件&#xff1a;回溯 难点&#xff1a; 这道题的难点在于如何去重&#xff0c;肯定不能像我们在组合中去重那样对数组排序。而且我们是要对每一层进行去重&#xff0c;…...

Elasticsearch 全文查询详解

全文查询&#xff08;Full-Text Query&#xff09;是 Elasticsearch 中的核心功能之一&#xff0c;用于对非结构化文本数据进行高效检索。与结构化查询不同&#xff0c;全文查询不仅仅是简单的精确匹配&#xff0c;还包括对文本进行分析和处理&#xff0c;从而实现更复杂的搜索…...

宁津哪个网络公司做网站比较好/百度搜索推广是什么

一.Linux 磁盘管理 Linux磁盘管理好坏直接关系到整个系统的性能问题。 Linux磁盘管理常用三个命令为df、du和fdisk。 df&#xff1a;列出文件系统的整体磁盘使用量du&#xff1a;检查磁盘空间使用量fdisk&#xff1a;用于磁盘分区df df命令参数功能&#xff1a;检查文件系统的磁…...

网站建设案例分析/做网站推广的公司

相关需求是: 1.从FTP上下载文件(此FTP不知因为什么原因.下载东东时二次连接才会连上一次.FTP软件是,在.NET程序下一样如此). 2.下载文件时先要看这个文件的控制文件在不在,在才下载,不在不下载. 3.解析文件每行数据.客户会给出这个字段在这行数据中的位置.如Name(0,8),Status(8…...

企业网站安全建设方案/东莞百度seo关键词优化

http://blog.csdn.net/slnqnd/article/details/1772910/ Struts2.0 Hibernate 3.2 Spring 2.0 一&#xff0e; Struts 1.定义 它是使用 servlet 和 JavaServer Pages 技术的一种 Model-View-Controller 实现&#xff0c; 可帮助您控制Web 项目中的变化并提高专业化水平。…...

软件工程技术学什么/网站性能优化的方法有哪些

效果图 静态图 ​ 动态图 ​ 代码及详解: 代码很简单,让我们直接来看代码和注释 varying vec2 texcoord;// uniform float iGlobalTime; // uniform vec2 iResolution;...

山西网站建设费用/今天的新闻摘抄

前言 今天讲解一下Android平台下ListView控件的开发&#xff0c;在本篇博客中&#xff0c;将介绍ListView的一些常用属性、方法及事件&#xff0c;还会讲解ListView在开发中常用的几种方式&#xff0c;以及使用不通用的适配器Adapter定制个性的View视图用于ListView的展示。 Li…...

越南国家建设部网站/正规淘宝代运营去哪里找

本文首发于政采云前端团队博客&#xff1a;五分钟看懂 Nginx 负载均衡https://www.zoo.team/article/nginx前言对于电商平台而言&#xff0c;随着业务的不断发展壮大&#xff0c;网站访问量和数据量也随之急剧增长&#xff0c;该情况的产生给服务器带来了一定的负担。从用户体验…...