深入理解Spring循环引用:三级缓存原理解决方案详解

什么是循环依赖

循环依赖,指的是一个或多个对象实例之间存在直接或间接的依赖关系,这种依赖关系构成了构成一个环形调用。

以下这种类似情况都属于循环引用:

循环依赖是与实际业务相关的,某些业务在实现的时候可能就是需要 Bean A依赖 Bean B,同时 Bean B 又依赖 Bean A,这是正常的情况,开发者在编程的时候可以尽量避免循环依赖,但如果遇到某个业务实在无法避免也没关系,Spring 本身可以自动解决一部分循环依赖的情况,只要正确的使用 Spring,即使有循环依赖也完全可以正常运行,关键在于我们需要弄清楚 Spring 中有哪些循环依赖的情况,以及 Spring 无法解决哪些情况。

如何检测循环依赖

Spring 检测循环依赖的方式也比较简单,在创建 Bean 的时候可以给一个标记,表明当前 Bean 正在创建,在属性填充阶段会递归的创建依赖的其他 Bean,递归创建的过程中如果发现所依赖的 Bean 已经处于创建中的状态,就说明存在循环依赖了,存在循环依赖并不会直接报错,只有 Spring 无法自动解决的时候才会报错。

能处理哪种循环依赖

两个对象都是单例,并且通过 set 方式注入才可以成功,通过构造方式是不行的;如果两个对象都是多实例的情况,那么不管是 set 还是构造都是不行的。其他方式的注入,例如使用构造的方式,或者为多例对象,启动时控制台均会报错。

解决循环依赖的原理

当出现循环依赖的时候可以先把依赖闭环中第一个 Bean 实例化并提前暴露出来,这样闭环上依赖这个 Bean 的其他 Bean 就可以创建完成,这个 Bean 创建出来之后,依赖这个 Bean 的下一个 Bean 也就可以创建出来,从而整个递归能够顺利进行下去,当跳出最后一层递归之后第一个 Bean 依赖的 Bean 也已经创建出来了,只要将这个 Bean 再注入到第一个 Bean 中整个闭环上所有 Bean 就都创建完成了。

举个例子,假如 A 依赖 B,B 依赖 C,C 又依赖 A,先把 A 实例化并提前暴露出来,这样 A 的引用已经有了并且能被其它 Bean 发现。由于 A 依赖 B,所以接下来尝试去创建 B,发现 B 又依赖 C,所以尝试去创建 C,发现 C 又依赖 A,这个时候发现 A 的引用已经有了,所以直接注入到 C,C 能顺利创建,C 创建完成之后 B 也就能顺利创建,B 创建完之后,将 B 注入 A,A 也就创建完成了,整个依赖闭环上的 Bean 都创建成功了。

整个过程的核心点就是提前暴露,存储在 Spring 的缓存中。整个过程共涉及到 Spring 提供的三级缓存,分别是:singletonObjects、earlySingletonObjects、singletonFactories。

三级缓存

我们称 Spring 为容器,容器从字面意义上就是存放东西的器皿,那 Spring 作为容器存放了什么呢?没错,就是 Bean,最终所有的单例 Bean 都会存放到 Spring 的单例池中。但是在 Spring 启动的时候单例 Bean 并不是一开始就存放到了单例池的,而是使用了三级缓存,创建的过程中会按照需要放入到不同的缓存,但最终都会移入到单例池中。同一个 Bean 同一个时刻只能存放在三级中的一个缓存中。单例池实际上就是三级缓存中的第一级缓存 singletonObjects,看 DefaultSingletonBeanRegistry 类的源码:

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

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

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

所谓三级缓存就是三个 Map,Spring 官方并没有直接指明层级,不过根据代码,singletonObjects 总是最先被查询,我们姑且称为一级缓存,以此类推,earlySingletonObjects 为二级缓存,singletonFactories 为三级缓存。

学习三级缓存的前提需要我们对 Spring 创建 Bean 的过程有不错的掌握,可以先看一下这篇文章:《揭秘Spring生命周期:Bean的创建过程超详细解析》。

在从 Spring 容器获取 Bean 的时候,如果是单例对象,我们会优先访问 getSingleton 方法从这三个缓存中获取:

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;
}

这三级缓存它们各自的含义如下:

  • singletonObjects:即一级缓存,也是最终存放所有单例 Bean 的单例池,所以这个缓存中存放的是经历了完整生命周期(创建实例、属性填充、初始化)创建过程,能够交付使用的完整的单例 Bean。
  • earlySingletonObjects:即二级缓存,属于临时缓存,用来存放实例化完成但是还没将属性装配完成的单例 Bean,也就是前面提到的那些提前暴露的 Bean,只有出现循环依赖的时候才需要把 Bean 提前暴露出来,所以也只有出现循环依赖的时候才会用到二级缓存。最终二级缓存中的所有 Bean 都会被移动到一级缓存中,所以最终这个缓存是空的。
  • singletonFactories:即三级缓存,属于临时缓存,存放ObjectFactoryObjectFactorygetObject()方法(最终调用的是getEarlyBeanReference()方法)可以生成原始 Bean 对象或者代理对象(如果 Bean 被 AOP 切面代理)。三级缓存只会对单例 Bean 生效。获取到对象之后就将移动到二级缓存中,避免多次创建对象。

一级缓存和二级缓存我们直接拿到的就是 Bean 对象,但是三级缓存明显是包装了一层,为什么要这么设计呢?我们稍后再解释。

一级缓存详解 singletonObjects

singletonObjects:即一级缓存,也是最终存放所有单例 Bean 的单例池,所以这个缓存中存放的是经历了完整生命周期(创建实例、属性填充、初始化)创建过程,能够交付使用的完整的单例 Bean。

我们不从循环依赖的角度去看一级缓存,我们先回顾一下 Spring 创建 Bean 的过程:

AbstractBeanFactory#doGetBean

// 为了简洁美观,这里将源码中的部分非核心内容进行了删减
protected <T> T doGetBean(String name, Class<T> requiredType, Object[] args, boolean typeCheckOnly) {
    
    // 转换名称,返回 Bean 的 ID
    final String beanName = transformedBeanName(name);
    ...
        
    // 单例 Bean 只会被创建一次,后续再获取 Bean,直接从单例缓存中获取(共有三级缓存)
    Object sharedInstance = getSingleton(beanName);
    ...
    
    // 父子容器的处理
    BeanFactory parentBeanFactory = getParentBeanFactory();
    parentBeanFactory.getBean(nameToLookup, requiredType);
	...
        
    // 获取合并后的父子 Bean,对应 <bean id="p" abstract="true"> 和 <bean id="c" parent="p" 的用法
    // 将子标签 bean 的各个属性合并到父标签,生成 RootBeanDefinition
    final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
    ...
        
    // 获取目标 bean 所依赖的其它 bean 名称
    String[] dependsOn = mbd.getDependsOn();
	...
        
    if (mbd.isSingleton()) {
        // 创建单例 Bean
        sharedInstance = getSingleton(beanName, () -> {
            try {
                // 创建 Bean 的核心方法
                return createBean(beanName, mbd, args);
            } // catch...
        });
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    } 
            
    else if (mbd.isPrototype()) {
        // 创建原型 Bean
        Object prototypeInstance = null;
        try {
            beforePrototypeCreation(beanName);
            prototypeInstance = createBean(beanName, mbd, args);
        } finally {
            afterPrototypeCreation(beanName);
        }
        bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
    } 
            
    else {
        // 创建其他作用域的 Bean
        String scopeName = mbd.getScope();
        final Scope scope = this.scopes.get(scopeName);
        if (scope == null) {
            throw new IllegalStateException(...);
        }
        try {
            Object scopedInstance = scope.get(beanName, () -> {
                beforePrototypeCreation(beanName);
                try {
                    return createBean(beanName, mbd, args);
                } finally {
                    afterPrototypeCreation(beanName);
                }
            });
            bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
        } // catch...
    }
    
    // 将 Bean 的类型转换为 getBean 时指定的 requireType 类型
	T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
    return (T) bean;
}

获取 Bean 的时候会优先从缓存中获取(依次查询三级缓存),缓存中不存在,则会执行创建逻辑,依次执行创建实例、属性填充、初始化。注意看下面的代码,访问的是 getSingleton 来创建并获取单例对象,传入 Bean 的名字和一个 Lambda 表达式。

    if (mbd.isSingleton()) {
        // 创建单例 Bean
        sharedInstance = getSingleton(beanName, () -> {
            try {
                // 创建 Bean 的核心方法
                return createBean(beanName, mbd, args);
            } // catch...
        });
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    } 
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
	Object singletonObject = this.singletonObjects.get(beanName);
	if (singletonObject == null) {
	    // 1.此处最终会调用到 AbstractAutowireCapableBeanFactory 类的 doCreateBean 方法真正去创建一个 Bean 的地方,也就是上面的 Lambda 表达式
		singletonObject = singletonFactory.getObject();
		newSingleton = true;
		
		if (newSingleton) {
		    // 2. 当 Bean 创建完成之后将 Bean 从三级缓存或二级缓存移入到一级缓存,整个创建过程就结束了
			addSingleton(beanName, singletonObject);
		}
	}
	return singletonObject;
}
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);
   }
}

从以上源码可以得知,创建的 Bean 是在完全创建并且属性填充好、执行初始化完成后,才会放进一级缓存中。

二级、三级缓存详解

earlySingletonObjects:即二级缓存,属于临时缓存,用来存放实例化完成但是还没将属性装配完成的单例 Bean,也就是前面提到的那些提前暴露的 Bean,只有出现循环依赖的时候才需要把 Bean 提前暴露出来,所以也只有出现循环依赖的时候才会用到二级缓存。最终二级缓存中的所有 Bean 都会被移动到一级缓存中,所以最终这个缓存是空的。

singletonFactories:即三级缓存,属于临时缓存,三级缓存这个 Map 的 Value 是 ObjectFactory 接口类型,ObjectFactory 中只有 getObject() 一个方法,我们从三级缓存中取出缓存的 ObjectFactory 之后,需要调用此方法来获取 Bean 对象,所以这个 ObjectFactory 相当于一个临时的容器,这个容器中只装了一个 Bean,所以三级缓存实际存的是当前 Bean 的一个临时容器。

二级、三级缓存的分析就要结合到循环依赖的情况了,我们还是要看一下真正在创建 Bean 时候的逻辑。前面说整体上创建 Bean 的时候有三步:创建实例、属性填充、初始化。

前面这篇文章:《解密Spring注入:@Autoware和@Value的实现原理》介绍了,注入其他依赖的 Bean 的时候发生在属性填充阶段,会递归从 Spring 工厂创建并获取所依赖的 Bean。在我们没有了解 Spring 缓存的情况下,我们想象的场景可能这个时候就出现死循环了。

举个例子:A、B 都是单例 Bean,且相互依赖。如果没有缓存,那么创建 A 的过程如下:

  1. 从工厂获取 A
  2. 实例化 A
  3. 属性填充 B
    1. 从工厂获取 B
    2. 实例化 B
    3. 属性填充 B
      1. 从工厂获取 A
      2. 实例化 A
      3. 属性填充 B

这不就死循环了,所以 Spring 这里通过提前将正在创建的 Bean 暴露出去,缓存起来解决这个问题。Java 都是通过对象引用关联的,所以在创建实例后,就可以缓存起来了。所以上面的步骤就变成了这样:

  1. 从工厂获取 A
  2. 实例化 A
  3. 缓存 A
  4. 属性填充 B
    1. 从工厂获取 B
    2. 实例化 B
    3. 属性填充 B
      1. 从工厂获取 A
      2. 从缓存获取 A
    4. 初始化 B
  5. 初始化 A
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Object[] args) {

    // 这里是一个关键点,使用对应的策略(工厂方法、构造函数)创建 Bean 实例,以及简单初始化
    // BeanWrapper 包装了 Bean 对象,并封装类型转换器
    instanceWrapper = createBeanInstance(beanName, mbd, args);
	...
        
	// 检查是否需要提前曝光,避免循环依赖
    // 注意第二个参数是一个 Lambda 表达式,实际上是 ObjectFactory 函数式接口,如果没有 AOP,返回的就是上面实例化的对象,但如果有AOP,返回的将是代理对象
	// 由于是一个 Lambda 表达式,所以 getEarlyBeanReference() 方法并不会立刻执行
    // 实际执行的时机是在获取 Bean 的入口,从缓存从拿的时候
	boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
	if (earlySingletonExposure) {
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}

    // 对 Bean 进行填充,将各个属性值注入,如果存在依赖的 Bean 则进行递归初始化
    populateBean(beanName, mbd, instanceWrapper);
    ...
    
    // 执行一系列的初始化方法
    exposedObject = initializeBean(beanName, exposedObject, mbd);
	...

	return exposedObject;
}

可以看到关键的代码,就是提前暴露,这里是将当前正在创建的 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);
        }
    }
}

这里使用的 ObjectFactory 将对象进行了包装,通过 getEarlyBeanReference 来获取对象。为什么要包装一层?这里主要是可以通过 BeanPostProcessors 进行一系列的处理,例如 AOP 的动态代理。

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

@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
	Object cacheKey = getCacheKey(bean.getClass(), beanName);
	// 注意 earlyProxyReferences 存放的是原来的 bean,而不是代理过后的 bean
    // 缓存提前创建的代理对象,之后在执行初始化的时候就不会重复创建代理对象了
	this.earlyProxyReferences.put(cacheKey, bean);
	// 生成代理对象
	return wrapIfNecessary(bean, beanName, cacheKey);
}

第三级的缓存只要是 Spring 基于 AOP 的考虑。

AOP 的底层原理是动态代理,通过代理来对功能进行增强,所以如果有 AOP 最终使用的必然是代理对象,比如我们对 A 进行了 AOP,那么最终注入到 B 的必然是 A 的代理对象。那么 A 的代理对象是什么时候生成的呢?如果没有循环依赖,AOP 的代理对象是在 Bean 后置处理器的后方法中生成的,Spring 执行 Bean后 置处理的后方法是在 initializeBean 方法中,而 initializeBean 方法是在 populateBean 方法的后面,populateBean 方法用来处理依赖关系,所以没有循环依赖的情况下 Spring 是先进行依赖注入,然后再生成代理对象。

但既然现在发生了循环依赖,所以需要提前就创建好代理对象,也就是从三级缓存中获取到的 ObjectFactory 的 getObject 方法,也就是这个 getEarlyBeanReference 的逻辑实现。

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;
}

三级缓存的设计可以很好的解决代理对象多次创建的情况(A 依赖 B,B 依赖 A;同时 A 依赖 C,C 依赖 A)在这种复杂的依赖关系的时候,第一次获取到 A 的代理是从三级缓存 singletonFactory.getObject() 获取,之后就被放在了二级缓存 earlySingletonObjects 中,其他依赖 A 的 Bean 注入 A 的代理对象的时候只需从二级缓存中获取即可。避免多次创建代理对象。

扩展问题

为什么不能解决构造器的循环依赖?

从前面的分析可以看到,单例 Bean 是在实例化后,也就是执行了构造方法后,才提前暴露的,因此使用构造器注入的话,三级缓存中并没有存放提前暴露的对象,因此 getBean 的时候,都不能从缓存中获取,所以 Spring 无法解决构造器参数注入导致的循环依赖。

为什么 prototype 不能解决循环依赖?

作用域为 prototype 的 Bean 并不是在容器启动的时候开始 Bean 的生命周期的,而是在用到的时候调用 doGetBean 方法才会走生命周期流程,从源码中可以看到,只有单例 Bean 才使用了三级缓存,原型 Bean 是没有缓存的,所以 Spring 无法解决 prototype 原型 Bean 属性注入导致的循环依赖。

如果只有一级缓存,能不能解决循环依赖问题?

不能。我们都知道一级缓存存放的是完整对象(实例化完成、属性填充完成、初始化完成),如果只有一级缓存的话,意味着半成品对象(实例化完成、属性未填充、未初始化)需要跟完整对象放在一起,这样调用 getBean() 就有可能拿到的是半成品对象,属性的值都是 null。所以只有一级缓存的话,是不能解决循环依赖问题的。

如果只有一级缓存、三级缓存的话,能不能解决循环依赖问题?

只使用一级缓存、三级缓存这两个缓存确实可以解决循环依赖,但是有一个前提,这个 Bean 没被 AOP 进行切面代理。

  • 如果不涉及到代理的话,只有一级缓存、三级缓存是可以解决循环依赖的。一级缓存存放完整对象,三级缓存存放提前暴露出来的对象。
  • 但是如果涉及到代理的时候,就不行了,因为三级缓存中的 ObjectFactory 每执行一次,就会新创建一个对象,不能解决循环依赖问题。

为什么需要使用二级缓存 earlySingletonObjects?

如果没有涉及到 AOP 代理,二级缓存好像显得有点多余。但是如果使用了 AOP 代理,那么二级缓存就发挥作用了。

前面提到,三级缓存 singletonFactories 中存放的是 ObjectFactory 对象工厂,当执行 singleFactory.getObject() 回调的时候,实际上会执行 getEarlyBeanReference() 方法获取 Bean 的早期引用,但是我们需要注意的是,每次执行 singleFactory.getObject() 方法都会重新产生一个新的代理对象,这就有问题了,因为我们的 Bean 是单例的,不可能每次都来一个新的代理对象。

所以,Spring 引入了二级缓存来解决这个问题,将执行了 singleFactory.getObject() 产生的对象放到二级缓存 earlySingletonObjects 中去,后面去二级缓存中拿,没必要再执行一遍 singletonFactory.getObject() 方法再产生一个新的代理对象,保证始终只有一个代理对象。

所以如果没有 AOP 的话确实可以两级缓存就可以解决循环依赖的问题,如果加上 AOP,两级缓存是无法解决的,不可能每次执行 singleFactory.getObject() 方法都给我产生一个新的代理对象,所以还要借助另外一个缓存来保存产生的代理对象。

三级缓存中为什么保存 ObjectFacory 对象,而不是保存原始的实例对象?

举个例子,假设直接将原始对象 A 存入三级缓存中,那么其他对象如 B 依赖了 A,在注入的时候,B 需要的 A 不是原始的 A 对象,这样就可能有问题。

如果存入的是一个 ObjectFactory 对象工厂,那么我们可以根据 ObjectFactory 生产任何 Bean 对象,ObjectFactory 是 Spring 留给我们进行扩展的,有可能我们需要对 Bean 进行代理或者其他操作。如当调用 singletonFactory.getObject() 方法的时候,会执行 getEarlyBeanReference() 方法,里面可以通过 BeanPostProcessor 后置处理器增强 Bean。

@Lazy 能解决循环依赖吗?

@Lazy 用来标识类是否需要懒加载/延迟加载,可以作用在类上、方法上、构造器上、方法参数上、成员变量中。

Spring Boot 2.2 新增了全局懒加载属性,开启后全局 bean 被设置为懒加载,需要时再去创建。

配置文件配置全局懒加载:

# 默认 false
spring.main.lazy-initialization=true

编码的方式设置全局懒加载:

SpringApplication springApplication = new SpringApplication(Start.class);
springApplication.setLazyInitialization(false);
springApplication.run(args);

如非必要,尽量不要用全局懒加载。全局懒加载会让 Bean 第一次使用的时候加载会变慢,并且它会延迟应用程序问题的发现(当 Bean 被初始化时,问题才会出现)。

如果一个 Bean 没有被标记为懒加载,那么它会在 Spring IoC 容器启动的过程中被创建和初始化。如果一个 Bean 被标记为懒加载,那么它不会在 Spring IoC 容器启动时立即实例化,而是在第一次被请求时才创建。这可以帮助减少应用启动时的初始化时间,也可以用来解决循环依赖问题。

循环依赖问题是如何通过@Lazy 解决的呢?这里举一个例子,比如说有两个 Bean,A 和 B,他们之间发生了循环依赖,那么 A 的构造器上添加 @Lazy 注解之后(延迟 Bean B 的实例化),加载的流程如下:

  • 首先 Spring 会去创建 A 的 Bean,创建时需要注入 B 的属性;
  • 由于在 A 上标注了 @Lazy 注解,因此 Spring 会去创建一个 B 的代理对象,将这个代理对象注入到 A 中的 B 属性;
  • 之后开始执行 B 的实例化、初始化,在注入 B 中的 A 属性时,此时 A 已经创建完毕了,就可以将 A 给注入进去。

通过 @Lazy 就解决了循环依赖的注入,关键点就在于对 A 中的属性 B 进行注入时,注入的是 B 的代理对象,因此不会循环依赖。

之前说的发生循环依赖是因为在对 A 中的属性 B 进行注入时,注入的是 B 对象,此时又会去初始化 B 对象,发现 B 又依赖了 A,因此才导致的循环依赖。

一般是不建议使用循环依赖的,但是如果项目比较复杂,可以使用 @Lazy 解决一部分循环依赖的问题。

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值