关于spring的循环依赖
errol发表于2025-07-20 | 分类为 编程 | 标签为springjava循环依赖

虽然spring已经设计了三级缓存机制尽可能地避免循环依赖(Circular dependencies),但在某些情况下,该问题仍然存在。

循环依赖,一般的解释是指两个或多个模块、组件或实体之间存在相互依赖的场景,但在本文,循环依赖指的是一种会导致应用无法正常启动的系统性问题

首先需要明确的是,“三级缓存解决循环依赖“的结果,是在”已经创建了对象“的条件下完成的。

先得把对象创建出来,再谈解决问题

下面,引用spring获取bean的源码进行说明。

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // Quick check for existing instance without full singleton lock.
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            if (!this.singletonLock.tryLock()) {
                // Avoid early singleton inference outside of original creation thread.
                return null;
            }
            try {
                // Consistent creation of early reference within full singleton lock.
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            singletonObject = singletonFactory.getObject();
                            // Singleton could have been added or removed in the meantime.
                            if (this.singletonFactories.remove(beanName) != null) {
                                this.earlySingletonObjects.put(beanName, singletonObject);
                            }
                            else {
                                singletonObject = this.singletonObjects.get(beanName);
                            }
                        }
                    }
                }
            }
            finally {
                this.singletonLock.unlock();
            }
        }
    }
    return singletonObject;
}

其中,singletonObjects为一级缓存,该集合保存了已经初始化完毕,可直接使用的bean;

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

earlySingletonObjects为二级缓存,保存了未初始化完成,但已经实例化的bean;

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

而所谓的三级缓存,其实就是用于创建bean的工厂方法或构造器;

/** Creation-time registry of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);

在向ioc容器请求bean时,首先会在一级缓存中查找,如果存在就直接返回;

Object singletonObject = this.singletonObjects.get(beanName);

然后再到二级缓存中查找,存在则返回,即便还未初始化完成;

singletonObject = this.singletonObjects.get(beanName);

还找不到时,最后从三级缓存中取出工厂方法或构造器(如果存在的话),创建对象并将其加入二级缓存;

// 获取工厂方法或构造器
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
// 创建bean
singletonObject = singletonFactory.getObject();
// 加入缓存
this.earlySingletonObjects.put(beanName, singletonObject);

以上便是经过简化的spring用于处理循环依赖的三级缓存机制。

然而,正是这种机制,会存在着这样的问题:如果现有A、B两个类,且它们是通过构造器互相注入参数的话,将会导致循环依赖,以下面的代码为例。

@Component
public class A {
    final private B b;

    A(B b) {
        this.b = b;
    }
}

@Component
public class B {
    final private A a;

    B(A a) {
        this.a = a;
    }
}

在创建a时,spring发现其构造函数中依赖到了B,于是开始创建b(spring的依赖注入机制【Dependency Injection】)。

注意,此时a还未实例化完成,因此,二级缓存中不存在对应的bean

而创建b时,spring又发现依赖到了A,接着转过头去创建a...

此时,spring就会检测到,无论是a还是b,都在处于创建状态中,没法完成依赖注入(DI)。

最终,抛出了循环依赖的异常。

image

图1 循环依赖异常

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference or an asynchronous initialization dependency? at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:544) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:312) ...

不过,要解决这个问题,其实也简单。

正如前面所说,三级缓存机制只是不能处理构造注入(Constructor-based Dependency Injection)方式互相引用的场景。

换个说法,只要避免互相引用的双方使用构造注入,让工厂方法或构造器完成bean的实例化即可。

事实上,除了构造注入外,spring还提供了其他的注入方式,如方法注入(Setter-based Dependency Injection)、字段注入(直接在字段上使用@Autowired、@Inject等注解标注)。

比方说,使用方法注入的方式,可以很好地解决这个问题:

只需要改动其中的一个

@Component
public class A {
    private B b;
    
    @Autowired
    public void setB(B b) {
        this.b = b;
    }
}

这样一来,a便可不依赖b完成实例化,同时加入二级缓存,此后,b也可以从缓存中取出a并注入(初始化稍后执行),最终程序得以正常运行。

public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        final String[] definitionNames = context.getBeanDefinitionNames();
        for (String name : definitionNames) {
            System.out.println(name);
        }
    }

image

图2 正常执行main方法的结果

总的来说,文中所举的例子只是一种极端的情况,在项目中并不常见,设计程序时稍作留意即可;而由于spring的三级缓存已经处理了大部分循环依赖的场景,一般情况下甚至都不会遇到循环依赖的问题。

以上。

返回