您当前的位置:首页 > 电脑百科 > 程序开发 > 架构

万字长文带你彻底吃透Spring循环依赖

时间:2023-12-06 16:36:53  来源:微信公众号  作者:冰河技术

一、学习指引

Spring中的循环依赖问题,你真的彻底了解过吗?

Spring的循环依赖问题可以说是面试过程中出现的非常频繁的问题。比如,在面试过程中面试官可能会问:有了解过Spring中的循环依赖问题吗?或者会问:什么是循环依赖问题?或者会问:Spring中在哪些场景下会产生循环依赖问题?或者会问:Spring是如何解决循环依赖问题的?看似轻描淡写的一句话,实际要考察的内容也是比较多的。面试官可以通过这个问题考察面试者是否研究过Spring的源码,有没有了解过Spring中的循环依赖问题。

文末扫码加星球立减¥200,仅限2023年12月,仅限前300名,先到先得。

本章,我们就一起来聊聊Spring中的循环依赖问题。

二、循环依赖概述

什么是循环依赖?

循环依赖其实也很好理解,可以将这个词拆分成两部分,一个是循环,一个是依赖。循环,顾名思义就是指形成了一个闭合环路,也就是闭环。依赖就是指某个事件的发生要依赖另一个事件。

在Spring中的循环依赖就是指一个或者多个Bean之间存在着互相依赖的关系,并且形成了循环调用。例如,在Spring中,A依赖B,B又依赖A,A和B之间就形成了相互依赖的关系。创建A对象时,发现A对象依赖了B对象,此时先去创建B对象。创建B对象时,发现B对象又依赖了A对象,此时又去创建A对象。创建A对象时,发现A对象依赖了B对象....如果Spring不去处理这种情况,就会发生死循环,一直会创建A对象和B对象,直到抛出异常为止。

同理,在Spring中多个对象之间也有可能存在循环依赖,例如,A依赖B,B依赖C,C又依赖A,A、B、C之间形成了互相依赖的关系,这也是一种循环依赖。

三、循环依赖类型

循环依赖有这些类型呢?

循环依赖总体上可以分成:自我依赖、直接依赖和间接依赖三种类型,如图20-1所示。

万字长文带你彻底吃透Spring循环依赖

3.1 自我依赖

自我依赖就是自己依赖自己,从而形成的循环依赖,一般情况下不会发生这种循环依赖,如图20-2所示。

万字长文带你彻底吃透Spring循环依赖

3.2 直接依赖

直接依赖一般是发生在两个对象之间,例如对象A依赖对象B,对象B又依赖对象A,对象A和对象B之间形成了依赖关系,如图20-3所示。

万字长文带你彻底吃透Spring循环依赖

3.3 间接依赖

间接依赖一般是发生在三个或三个以上对象之间互相依赖的场景,例如对象A依赖对象B,对象B依赖对象C,对象C又依赖对象A,对象A、对象B和对象C之间就形成了循环依赖,如图20-4所示。

万字长文带你彻底吃透Spring循环依赖

四、循环依赖场景

Spring中有哪些循环依赖的场景?

Spring中的循环依赖场景总体上可以分成单例Bean的setter循环依赖、多例Bean的setter循环依赖、代理对象的setter循环依赖、构造方法的循环依赖和DependsOn的循环依赖。如图20-5所示。

万字长文带你彻底吃透Spring循环依赖

4.1 单例Bean的setter循环依赖的特殊情况

Spring是支持基于单例Bean的setter方法的循环依赖的,不过有一种特殊情况需要注意。本节,我们就一起实现基于单例Bean的setter方法的循环依赖的特殊情况,具体实现步骤如下所示。

(1)新增SpecialCircularBeanA类

SpecialCircularBeanA类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.special.bean.SpecialCircularBeanA。

@Component
public class SpecialCircularBeanA {
    @Autowired
    private SpecialCircularBeanB specialCircularBeanB;
    @Override
    public String toString() {
        return "SpecialCircularBeanA{" +
                "specialCircularBeanB=" + specialCircularBeanB +
                '}';
    }
}

可以看到,在SpecialCircularBeanA类上只标注了@Component注解,所以在IOC容器启动时,会在IOC容器中创建SpecialCircularBeanA类型的单例Bean,并且在SpecialCircularBeanA类型的Bean对象中,会依赖SpecialCircularBeanB类型的Bean对象。同时,在SpecialCircularBeanA类中重写了toString()方法,打印了依赖的SpecialCircularBeanB类型的对象。

(2)新增SpecialCircularBeanB类

SpecialCircularBeanB类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.special.bean.SpecialCircularBeanB。

@Component
public class SpecialCircularBeanB {
    @Autowired
    private SpecialCircularBeanA specialCircularBeanA;
    @Override
    public String toString() {
        return "SpecialCircularBeanB{" +
                "specialCircularBeanA=" + specialCircularBeanA +
                '}';
    }
}

可以看到,在SpecialCircularBeanB类上只标注了@Component注解,所以在IOC容器启动时,会在IOC容器中创建SpecialCircularBeanB类型的单例Bean,并且在SpecialCircularBeanB类型的Bean对象中,会依赖SpecialCircularBeanA类型的Bean对象。同时,在SpecialCircularBeanB类中重写了toString()方法,打印了依赖的SpecialCircularBeanA类型的对象。

(3)新增SpecialCircularConfig类

SpecialCircularConfig类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.special.config.SpecialCircularConfig。

@Configuration
@ComponentScan(value = {"io.binghe.spring.annotation.chapter20.special"})
public class SpecialCircularConfig {
}

可以看到,在SpecialCircularConfig类上标注了@Configuration注解,说明SpecialCircularConfig类是案例程序的配置类,并且使用@ComponentScan注解指定了扫描的包。

(4)新增SpecialCircularTest类

SpecialCircularTest类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.special.SpecialCircularTest。

public class SpecialCircularTest {
    public static void mAIn(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpecialCircularConfig.class);
        SpecialCircularBeanA specialCircularBeanA = context.getBean(SpecialCircularBeanA.class);
        System.out.println(specialCircularBeanA);
        context.close();
    }
}

可以看到,在SpecialCircularTest类的main()方法中,传入SpecialCircularConfig类的Class对象后,创建IOC容器,随后从IOC容器中获取SpecialCircularBeanA类型的Bean对象并进行打印,最后关闭IOC容器。

(5)运行SpecialCircularTest类

运行SpecialCircularTest类的main()方法,输出的结果信息如下所示。

Exception in thread "main" JAVA.lang.StackOverflowError

可以看到,程序抛出了StackOverflowError异常。

其实,从本质上讲,这个异常不是Spring抛出的,而是JVM抛出的栈溢出错误。

4.2 多例Bean的setter循环依赖

Spring是不支持基于多例Bean,也就是原型模式下的Bean的setter方法的循环依赖。本节,我们一起实现一个基于多例Bean的set方法的循环依赖案例,具体实现步骤如下所示。

(1)新增PrototypeCircularBeanA类

PrototypeCircularBeanA类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.prototype.bean.PrototypeCircularBeanA。

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeCircularBeanA {
    @Autowired
    private PrototypeCircularBeanB prototypeCircularBeanB;

    public PrototypeCircularBeanB getPrototypeCircularBeanB() {
        return prototypeCircularBeanB;
    }
}

可以看到,PrototypeCircularBeanA类的对象在Spring中是多例Bean,并且依赖了PrototypeCircularBeanB类型的Bean对象,并提供了getPrototypeCircularBeanB()方法返回PrototypeCircularBeanB类型的Bean对象。

(2)新增PrototypeCircularBeanB类

PrototypeCircularBeanB类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.prototype.bean.PrototypeCircularBeanB。

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeCircularBeanB {
    @Autowired
    private PrototypeCircularBeanA prototypeCircularBeanA;
    
    public PrototypeCircularBeanA getPrototypeCircularBeanA() {
        return prototypeCircularBeanA;
    }
}

可以看到,PrototypeCircularBeanB类的对象在Spring中是多例Bean,并且依赖了PrototypeCircularBeanA类型的Bean对象,并提供了getPrototypeCircularBeanA()方法返回PrototypeCircularBeanA类型的Bean对象。

(3)新增PrototypeCircularConfig类

PrototypeCircularConfig类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.prototype.config.PrototypeCircularConfig。

@Configuration
@ComponentScan(value = {"io.binghe.spring.annotation.chapter20.prototype"})
public class PrototypeCircularConfig {
}

可以看到,在PrototypeCircularConfig类上标注了@Configuration注解,说明PrototypeCircularConfig类是案例程序的配置类,并且使用@ComponentScan注解指定了要扫描的包名。

(4)新增PrototypeCircularTest类

PrototypeCircularTest类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.prototype.PrototypeCircularTest。

public class PrototypeCircularTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PrototypeCircularConfig.class);
        PrototypeCircularBeanA prototypeCircularBeanA = context.getBean(PrototypeCircularBeanA.class);
        System.out.println("prototypeCircularBeanA===>>>" + prototypeCircularBeanA);
        System.out.println("prototypeCircularBeanB===>>>" + prototypeCircularBeanA.getPrototypeCircularBeanB());
        context.close();
    }
}

可以看到,在PrototypeCircularTest类的main()方法中,创建完IOC容器后,会从IOC容器中获取PrototypeCircularBeanA类型的Bean对象,并打印PrototypeCircularBeanA类型的Bean对象和依赖的PrototypeCircularBeanB类型的Bean对象。

(5)运行PrototypeCircularTest类

运行PrototypeCircularTest类的main()方法,输出的结果信息如下所示。

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'prototypeCircularBeanA': Unsatisfied dependency expressed through field 'prototypeCircularBeanB': Error creating bean with name 'prototypeCircularBeanB': Unsatisfied dependency expressed through field 'prototypeCircularBeanA': Error creating bean with name 'prototypeCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
/***************省略其他输出信息**************/
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'prototypeCircularBeanB': Unsatisfied dependency expressed through field 'prototypeCircularBeanA': Error creating bean with name 'prototypeCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
/***************省略其他输出信息**************/
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'prototypeCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
/***************省略其他输出信息**************/

可以看到,输出结果中打印出了循环依赖的异常信息,说明Spring不支持多例Bean的setter方法循环依赖。

4.3 代理对象的setter循环依赖

Spring默认是不支持基于代理对象的setter方法的循环依赖,本节,就简单实现一个基于代理对象的setter方法的循环依赖案例。具体实现步骤如下所示。

(1)新增ProxyCircularBeanA类

ProxyCircularBeanA类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.proxy.bean.ProxyCircularBeanA。

@Component
public class ProxyCircularBeanA {
    @Autowired
    private ProxyCircularBeanB proxyCircularBeanB;

    @Async
    public ProxyCircularBeanB getProxyCircularBeanB() {
        return proxyCircularBeanB;
    }
}

可以看到,ProxyCircularBeanA类型的Bean对象会依赖ProxyCircularBeanB类型的Bean对象,并且在getProxyCircularBeanB()方法上标注了@Async注解,当调用getProxyCircularBeanB()方法时,会通过AOP自动生成代理对象。

(2)新增ProxyCircularBeanB类

ProxyCircularBeanB类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.proxy.bean.ProxyCircularBeanB。

@Component
public class ProxyCircularBeanB {
    @Autowired
    private ProxyCircularBeanA proxyCircularBeanA;

    @Async
    public ProxyCircularBeanA getProxyCircularBeanA() {
        return proxyCircularBeanA;
    }
}

可以看到,ProxyCircularBeanB类型的Bean对象会依赖ProxyCircularBeanA类型的Bean对象,并且在getProxyCircularBeanA()方法上标注了@Async注解,当调用getProxyCircularBeanA()方法时,会通过AOP自动生成代理对象。

(3)新增ProxyCircularConfig类

ProxyCircularConfig类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.proxy.config.ProxyCircularConfig。

@EnableAsync
@Configuration
@ComponentScan(value = {"io.binghe.spring.annotation.chapter20.proxy"})
public class ProxyCircularConfig {
}

可以看到,在ProxyCircularConfig类上标注了@Configuration注解,说明ProxyCircularConfig类是案例程序的配置类。并且在ProxyCircularConfig类上使用@ComponentScan注解指定了要扫描的包名。同时,使用@EnableAsync注解开启了异步调用。

(4)新增ProxyCircularTest类

ProxyCircularTest类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.proxy.ProxyCircularTest。

public class ProxyCircularTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProxyCircularConfig.class);
        ProxyCircularBeanA proxyCircularBeanA = context.getBean(ProxyCircularBeanA.class);
        System.out.println("proxyCircularBeanA===>>>" + proxyCircularBeanA);
        System.out.println("proxyCircularBeanB===>>>" + proxyCircularBeanA.getProxyCircularBeanB());
        context.close();
    }
}

可以看到,在ProxyCircularTest类的main()方法中,创建完IOC容器后,会从IOC容器中获取ProxyCircularBeanA类型的Bean对象,并打印ProxyCircularBeanA类型的Bean对象和依赖的ProxyCircularBeanB类型的Bean对象。

(5)运行ProxyCircularTest类

运行ProxyCircularTest类的main()方法,输出的结果信息如下所示。

Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'proxyCircularBeanA': Bean with name 'proxyCircularBeanA' has been injected into other beans [proxyCircularBeanB] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.

从输出的结果信息可以看出,Spring抛出了循环依赖的异常。说明Spring默认不支持基于代理对象的setter方法的循环依赖。

4.4 构造方法的循环依赖

Spring不支持基于构造方法的循环依赖。本节,就简单实现一个基于构造方法的循环依赖,具体实现步骤如下所示。

(1)新增ConstructCircularBeanA类

ConstructCircularBeanA类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.construct.bean.ConstructCircularBeanA。

@Component
public class ConstructCircularBeanA {
    
    private ConstructCircularBeanB constructCircularBeanB;

    private ConstructCircularBeanA(ConstructCircularBeanB constructCircularBeanB){
        this.constructCircularBeanB = constructCircularBeanB;
    }

    public ConstructCircularBeanB getConstructCircularBeanB() {
        return constructCircularBeanB;
    }
}

可以看到,在ConstructCircularBeanA类中通过构造方法依赖了ConstructCircularBeanB类型的Bean对象,并提供了getConstructCircularBeanB()方法来获取ConstructCircularBeanB类型的Bean对象。

(2)新增ConstructCircularBeanB类

ConstructCircularBeanB类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.construct.bean.ConstructCircularBeanB。

@Component
public class ConstructCircularBeanB {

    private ConstructCircularBeanA constructCircularBeanA;

    public ConstructCircularBeanB(ConstructCircularBeanA constructCircularBeanA) {
        this.constructCircularBeanA = constructCircularBeanA;
    }

    public ConstructCircularBeanA getConstructCircularBeanA() {
        return constructCircularBeanA;
    }
}

可以看到,在ConstructCircularBeanB类中通过构造方法依赖了ConstructCircularBeanA类型的Bean对象,并提供了getConstructCircularBeanA()方法来获取ConstructCircularBeanA类型的Bean对象。

(3)新增ConstructCircularConfig类

ConstructCircularConfig类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.construct.config.ConstructCircularConfig。

@Configuration
@ComponentScan(value = {"io.binghe.spring.annotation.chapter20.construct"})
public class ConstructCircularConfig {
}

可以看到,在ConstructCircularConfig类上标注了@Configuration注解,说明ConstructCircularConfig类是案例程序的配置类,并且使用@ComponentScan注解指定了要扫描的包名。

(4)新增ConstructCircularTest类

ConstructCircularTest类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.construct.ConstructCircularTest。

public class ConstructCircularTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConstructCircularConfig.class);
        ConstructCircularBeanA constructCircularBeanA = context.getBean(ConstructCircularBeanA.class);
        System.out.println("cnotallow===>>>" + constructCircularBeanA);
        System.out.println("cnotallow===>>>" + constructCircularBeanA.getConstructCircularBeanB());
        context.close();
    }
}

可以看到,在ConstructCircularTest类的main()方法中,创建完IOC容器后,会从IOC容器中获取ConstructCircularBeanA类型的Bean对象,并打印ConstructCircularBeanA类型的Bean对象和依赖的ConstructCircularBeanB类型的Bean对象。

(5)运行ConstructCircularTest类

运行ConstructCircularTest类的main()方法,输出的结果信息如下所示。

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'constructCircularBeanA' defined in file [D:Workspaces2022springspring-annotation-bookspring-annotationspring-annotation-chapter-20targetclassesiobinghespringannotationchapter20constructbeanConstructCircularBeanA.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'constructCircularBeanB' defined in file [D:Workspaces2022springspring-annotation-bookspring-annotationspring-annotation-chapter-20targetclassesiobinghespringannotationchapter20constructbeanConstructCircularBeanB.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'constructCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
/*************省略其他信息**************/
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'constructCircularBeanB' defined in file [D:Workspaces2022springspring-annotation-bookspring-annotationspring-annotation-chapter-20targetclassesiobinghespringannotationchapter20constructbeanConstructCircularBeanB.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'constructCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
/*************省略其他信息**************/
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'constructCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?

可以看到,Spring抛出了循环依赖的异常,说明Spring不支持基于构造方法的循环依赖。

4.5 @DependsOn的循环依赖

@DependsOn注解主要用于指定Bean的实例化顺序,Spring默认是不支持基于@DependsOn注解的循环依赖。本节,就实现基于@DependsOn注解的循环依赖。具体实现步骤如下所示。

(1)新增DependsOnCircularBeanA类

DependsOnCircularBeanA类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.dependson.bean.DependsOnCircularBeanA。

@Component
@DependsOn("dependsOnCircularBeanB")
public class DependsOnCircularBeanA {
    @Autowired
    private DependsOnCircularBeanB dependsOnCircularBeanB;

    public DependsOnCircularBeanB getDependsOnCircularBeanB() {
        return dependsOnCircularBeanB;
    }
}

可以看到,在DependsOnCircularBeanA类上不仅标注了@Component注解,也标注了@DependsOn注解指定依赖dependsOnCircularBeanB,并且在DependsOnCircularBeanA类中使用@Autowired注解注入了DependsOnCircularBeanB类型的Bean对象。同时,在DependsOnCircularBeanA类中提供了getDependsOnCircularBeanB()方法获取DependsOnCircularBeanB类型的Bean对象。

(2)新增DependsOnCircularBeanB类

DependsOnCircularBeanB类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.dependson.bean.DependsOnCircularBeanB。

@Component
@DependsOn("dependsOnCircularBeanA")
public class DependsOnCircularBeanB {
    @Autowired
    private DependsOnCircularBeanA dependsOnCircularBeanA;

    public DependsOnCircularBeanA getDependsOnCircularBeanA() {
        return dependsOnCircularBeanA;
    }
}

可以看到,在DependsOnCircularBeanB类上不仅标注了@Component注解,也标注了@DependsOn注解指定依赖dependsOnCircularBeanA,并且在DependsOnCircularBeanB类中使用@Autowired注解注入了DependsOnCircularBeanA类型的Bean对象。同时,在DependsOnCircularBeanB类中提供了getDependsOnCircularBeanA()方法获取DependsOnCircularBeanA类型的Bean对象。

(3)新增DependsOnCircularConfig类

DependsOnCircularConfig类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.dependson.config.DependsOnCircularConfig。

@Configuration
@ComponentScan(value = {"io.binghe.spring.annotation.chapter20.dependson"})
public class DependsOnCircularConfig {
}

可以看到,在DependsOnCircularConfig类上标注了@Configuration注解,说明DependsOnCircularConfig类是案例程序的配置类。同时,使用@ComponentScan注解指定了要扫描的包名。

(4)新增DependsOnCircularTest类

DependsOnCircularTest类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.dependson.DependsOnCircularTest。

public class DependsOnCircularTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DependsOnCircularConfig.class);
        DependsOnCircularBeanA dependsOnCircularBeanA = context.getBean(DependsOnCircularBeanA.class);
        System.out.println("dependsOnCircularBeanA===>>>" + dependsOnCircularBeanA);
        System.out.println("dependsOnCircularBeanB===>>>" + dependsOnCircularBeanA.getDependsOnCircularBeanB());
        context.close();
    }
}

可以看到,在DependsOnCircularTest类的main()方法中,创建完IOC容器后,会从IOC容器中获取DependsOnCircularBeanA类型的Bean对象,并打印DependsOnCircularBeanA类型的Bean对象和依赖的DependsOnCircularBeanB类型的Bean对象。

(5)运行DependsOnCircularTest类

运行DependsOnCircularTest类的main()方法,输出的结果信息如下所示。

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dependsOnCircularBeanB' defined in file [D:Workspaces2022springspring-annotation-bookspring-annotationspring-annotation-chapter-20targetclassesiobinghespringannotationchapter20dependsonbeanDependsOnCircularBeanB.class]: Circular depends-on relationship between 'dependsOnCircularBeanB' and 'dependsOnCircularBeanA'

从输出的结果信息可以看出,Spring抛出了循环依赖的异常。说明Spring不支持@DependsOn注解的循环依赖。

4.6 单例Bean的setter循环依赖

Spring支持基于单例Bean的setter方法的循环依赖。本节,就实现基于单例Bean的setter方法的循环依赖案例。具体实现步骤如下所示。

(1)新增SingletonCircularBeanA类

SingletonCircularBeanA类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.singleton.bean.SingletonCircularBeanA。

@Component
public class SingletonCircularBeanA {
    @Autowired
    private SingletonCircularBeanB singletonCircularBeanB;

    public SingletonCircularBeanB getSingletonCircularBeanB() {
        return singletonCircularBeanB;
    }
}

可以看到,在SingletonCircularBeanA类中依赖了SingletonCircularBeanB类型的Bean对象,并提供了getSingletonCircularBeanB()方法获取SingletonCircularBeanB类型的Bean对象。

(2)新增SingletonCircularBeanB类

SingletonCircularBeanB类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.singleton.bean.SingletonCircularBeanB。

@Component
public class SingletonCircularBeanB {
    @Autowired
    private SingletonCircularBeanA singletonCircularBeanA;

    public SingletonCircularBeanA getSingletonCircularBeanA() {
        return singletonCircularBeanA;
    }
}

可以看到,在SingletonCircularBeanB类中依赖了SingletonCircularBeanA类型的Bean对象,并提供了getSingletonCircularBeanA()方法获取SingletonCircularBeanA类型的Bean对象。

(3)新增SingletonCircularConfig类

SingletonCircularConfig类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.singleton.config.SingletonCircularConfig。

@Configuration
@ComponentScan(value = {"io.binghe.spring.annotation.chapter20.singleton"})
public class SingletonCircularConfig {
}

可以看到,在SingletonCircularConfig类上标注了@Configuration注解,说明SingletonCircularConfig类是案例程序的配置类,并使用@ComponentScan注解指定了要扫描的包名。

(4)新增SingletonCircularTest类

SingletonCircularTest类的源码详见:spring-annotation-chapter-20工程下的io.binghe.spring.annotation.chapter20.singleton.SingletonCircularTest。

public class SingletonCircularTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SingletonCircularConfig.class);
        SingletonCircularBeanA singletonCircularBeanA = context.getBean(SingletonCircularBeanA.class);
        System.out.println("singletnotallow===>>>" + singletonCircularBeanA);
        System.out.println("singletnotallow===>>>" + singletonCircularBeanA.getSingletonCircularBeanB());
        context.close();
    }
}

可以看到,在SingletonCircularTest类的main()方法中,创建完IOC容器后,会从IOC容器中获取SingletonCircularBeanA类型的Bean对象,并打印SingletonCircularBeanA类型的Bean对象和依赖的SingletonCircularBeanB类型的Bean对象。

(5)运行SingletonCircularTest类

运行SingletonCircularTest类的main()方法,输出的结果信息如下所示。

singletnotallow===>>>io.binghe.spring.annotation.chapter20.singleton.bean.SingletonCircularBeanA@41e1e210
singletnotallow===>>>io.binghe.spring.annotation.chapter20.singleton.bean.SingletonCircularBeanB@be35cd9

可以看到,正确输出了SingletonCircularBeanA类型的Bean对象和SingletonCircularBeanB类型的Bean对象。**说明Spring支持基于单例Bean的setter方法的循环依赖。

五、Spring循环依赖底层解决方案分析

Spring底层是如何解决循环依赖问题的?

由循环依赖场景的分析得知:Spring除了默认支持基于单例Bean的setter方法的循环依赖外,默认均不支持其他情况下的循环依赖。但是,如果是通过标注@Async注解生成的代理对象,则可以通过将标注了@Async注解的类排到后面加载的IOC容器中即可解决循环依赖的问题。

5.1 不支持单例Bean的setter循环依赖的特殊情况

运行4.1节中SpecialCircularTest类的main()方法,输出的结果信息如下所示。

Exception in thread "main" java.lang.StackOverflowError

可以看到,实际抛出的是StackOverflowError错误。这个错误信息本质上不是Spring抛出的,而是JVM抛出的。根本原因其实是出在SpecialCircularBeanA和SpecialCircularBeanB两个类的toString()方法上。

当在SpecialCircularTest类的main()方法中打印specialCircularBeanA时,默认会调用SpecialCircularBeanA类的toString()方法,在SpecialCircularBeanA类的toString()方法中,会拼接specialCircularBeanB对象,此时又会调用SpecialCircularBeanB类的toString()方法。而在SpecialCircularBeanB类的toString()方法中,又会拼接specialCircularBeanA对象,此时又会调用SpecialCircularBeanA类的toString()方法。在SpecialCircularBeanA类的toString()方法中,又会拼接specialCircularBeanB对象,继而调用SpecialCircularBeanB类的toString()方法....如此反复,造成了死循环。

简单点说,就是在SpecialCircularBeanA类的toString()方法中调用了SpecialCircularBeanB类的toString()方法,在SpecialCircularBeanB类的toString()方法中调用了SpecialCircularBeanA类的toString()方法,造成了死循环,最终抛出StackOverflowError错误。

5.2 不支持多例Bean的setter循环依赖

运行4.2节中PrototypeCircularTest类的main()方法,输出的结果信息如下所示。

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'prototypeCircularBeanA': Unsatisfied dependency expressed through field 'prototypeCircularBeanB': Error creating bean with name 'prototypeCircularBeanB': Unsatisfied dependency expressed through field 'prototypeCircularBeanA': Error creating bean with name 'prototypeCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
/***************省略其他输出信息**************/
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'prototypeCircularBeanB': Unsatisfied dependency expressed through field 'prototypeCircularBeanA': Error creating bean with name 'prototypeCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
/***************省略其他输出信息**************/
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'prototypeCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
/***************省略其他输出信息**************/

Spring抛出了循环依赖的异常,说明Spring不支持多例Bean的setter循环依赖。接下来就分析下Spring中为啥不支持多例Bean的setter循环依赖。具体分析步骤如下所示。

(1)解析AbstractBeanFactory类的doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)方法

源码详见:org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)。先来看在doGetBean()方法中创建多例Bean对象的逻辑,如下所示。

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
        /**********省略其他代码*************/
    }
    else {
        /**********省略其他代码*************/
        try {
            /**********省略其他代码*************/
            else if (mbd.isPrototype()) {
                Object prototypeInstance = null;
                try {
                    beforePrototypeCreation(beanName);
                    prototypeInstance = createBean(beanName, mbd, args);
                }
                finally {
                    afterPrototypeCreation(beanName);
                }
                beanInstance = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
            }
  /**********省略其他代码*************/
        }
        catch (BeansException ex) {
            /**********省略其他代码*************/
        }
        finally {
           /**********省略其他代码*************/
        }
    }
    return adaptBeanInstance(name, beanInstance, requiredType);
}

可以看到,在多例Bean模式下,创建Bean对象之前会调用beforePrototypeCreation()方法,在创建Bean对象之后会调用afterPrototypeCreation()方法。

(2)解析AbstractBeanFactory类的beforePrototypeCreation(String beanName)方法

源码详见:org.springframework.beans.factory.support.AbstractBeanFactory#beforePrototypeCreation(String beanName)。

protected void beforePrototypeCreation(String beanName) {
    Object curVal = this.prototypesCurrentlyInCreation.get();
    if (curVal == null) {
        this.prototypesCurrentlyInCreation.set(beanName);
    }
    else if (curVal instanceof String strValue) {
        Set<String> beanNameSet = new HashSet<>(2);
        beanNameSet.add(strValue);
        beanNameSet.add(beanName);
        this.prototypesCurrentlyInCreation.set(beanNameSet);
    }
    else {
        Set<String> beanNameSet = (Set<String>) curVal;
        beanNameSet.add(beanName);
    }
}

可以看到,Spring在创建多例Bean时,会在beforePrototypeCreation()方法中,使用prototypesCurrentlyInCreation记录正在创建中的Bean,那prototypesCurrentlyInCreation又是个什么鬼呢?

prototypesCurrentlyInCreation的源码详见:org.springframework.beans.factory.support.AbstractBeanFactory#prototypesCurrentlyInCreation,如下所示。

private final ThreadLocal<Object> prototypesCurrentlyInCreation = new NamedThreadLocal<>("Prototype beans currently in creation");

也就是说,Spring在创建多例Bean时,会使用一个ThreadLocal类型的变量prototypesCurrentlyInCreation来记录当前线程正在创建中的Bean。并且根据beforePrototypeCreation()方法的源码又可以看出,在prototypesCurrentlyInCreation变量中使用一个Set集合来存储正在创建中的Bean。由于Set集合不存在重复对象,所以这样就能够保证在一个线程中只能有一个相同的Bean正在被创建。

(3)解析AbstractBeanFactory类的afterPrototypeCreation(String beanName)方法

源码详见:org.springframework.beans.factory.support.AbstractBeanFactory#afterPrototypeCreation(String beanName)。

protected void afterPrototypeCreation(String beanName) {
    Object curVal = this.prototypesCurrentlyInCreation.get();
    if (curVal instanceof String) {
        this.prototypesCurrentlyInCreation.remove();
    }
    else if (curVal instanceof Set<?> beanNameSet) {
        beanNameSet.remove(beanName);
        if (beanNameSet.isEmpty()) {
            this.prototypesCurrentlyInCreation.remove();
        }
    }
}

可以看到,afterPrototypeCreation()方法中主要是对prototypesCurrentlyInCreation中存储的Bean进行移除操作。

综合beforePrototypeCreation()方法和afterPrototypeCreation()方法可以看出,Spring在创建多例Bean之前,会将当前线程正在创建的Bean存入prototypesCurrentlyInCreation中,待Bean对象实例化完成后,就从prototypesCurrentlyInCreation中移除正在创建的Bean。

(4)返回AbstractBeanFactory类的doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)方法。此时重点关注doGetBean()方法开始部分的代码片段。

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)throws BeansException {
    String beanName = transformedBeanName(name);
    Object beanInstance;
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
         /**********省略其他代码*************/
        beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }
    else {
        if (isPrototypeCurrentlyInCreation(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
        }
    /**********省略其他代码*************/
    }
    return adaptBeanInstance(name, beanInstance, requiredType);
}

在AbstractBeanFactory类的doGetBean()方法开始的部分,会调用getSingleton()方法从缓存中获取单例Bean对象,首先会执行if条件进行判断,如果获取到的单例Bean对象为空,说明此时可能是第一次执行doGetBean()方法,也可能是创建的多例Bean。接下来会进入else分支逻辑,在else分支逻辑中,首先会判断当前线程是否已经存在正在创建的Bean,如果存在,则直接抛出BeanCurrentlyInCreationException异常。

(5)解析AbstractBeanFactory类的isPrototypeCurrentlyInCreation(String beanName)方法

源码详见:org.springframework.beans.factory.support.AbstractBeanFactory#isPrototypeCurrentlyInCreation(String beanName)。

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
    Object curVal = this.prototypesCurrentlyInCreation.get();
    return (curVal != null && (curVal.equals(beanName) || (curVal instanceof Set<?> set && set.contains(beanName))));
}

所以,在Spring创建多例Bean时,无法解决Bean的循环依赖。如果在创建多例Bean的过程中,发现存在循环依赖,则直接抛出BeanCurrentlyInCreationException异常。

5.3 不支持代理对象的setter循环依赖

运行4.3节中ProxyCircularTest类的main()方法,输出的结果信息如下所示。

Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'proxyCircularBeanA': Bean with name 'proxyCircularBeanA' has been injected into other beans [proxyCircularBeanB] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.

从输出的结果信息可以看出,Spring抛出了循环依赖的异常。接下来,具体分析Spring为何不支持代理对象的setter循环依赖。

(1)解析AbstractAutowireCapableBeanFactory类的doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)方法。

通过前面章节对于创建Bean的流程分析可知,无论是创建单例Bean还是创建多例Bean,Spring都会执行到AbstractAutowireCapableBeanFactory类的doCreateBean()方法中。

源码详见:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)。重点关注如下代码片段。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
 /********省略其他代码**********/ 
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                      isSingletonCurrentlyInCreation(beanName));
    /********省略其他代码**********/
    if (earlySingletonExposure) {
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            if (exposedObject == bean) {
                exposedObject = earlySingletonReference;
            }
            else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
                String[] dependentBeans = getDependentBeans(beanName);
                Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
                for (String dependentBean : dependentBeans) {
                    if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
                        actualDependentBeans.add(dependentBean);
                    }
                }
                if (!actualDependentBeans.isEmpty()) {
                    throw new BeanCurrentlyInCreationException(beanName, "Bean with name '" + beanName + "' has been injected into other beans [" + StringUtils.collectionToCommaDelimitedString(actualDependentBeans) + reference, but has eventually been " + "wrapped. This means that said other beans do not use the final version of the " + "bean. This is often the result of over-eager type matching - consider using " +"'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.");
                }
            }
        }
    }
 /********省略其他代码**********/
    return exposedObject;
}

可以看到,在AbstractAutowireCapableBeanFactory类的doCreateBean()方法中,会判断从二级缓存中获取到的对象是否等于原始对象,代码片段如下所示。

Object earlySingletonReference = getSingleton(beanName, false);
if (earlySingletonReference != null) {
    if (exposedObject == bean) {
        exposedObject = earlySingletonReference;
    }

此时,如果是代理对象的话,二级缓存中会存放由AOP生成出来的代理对象,与原始对象不相等。所以,会抛出BeanCurrentlyInCreationException异常。

另外,仔细观察AbstractAutowireCapableBeanFactory类的doCreateBean()方法时,会发现如果从二级缓存中获取到的earlySingletonReference对象为空,就会直接返回,不会抛出BeanCurrentlyInCreationException异常。由于Spring默认会按照文件全路径递归搜索,并且会按照路径+文件名的方式进行排序,排序靠前的Bean先被加载。所以,将标注了@Async注解的类排在后面即可解决循环依赖的问题。

5.4 不支持构造方法的循环依赖

运行4.4节中ConstructCircularTest类的main()方法,输出的结果信息如下所示。

Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'constructCircularBeanA' defined in file [D:Workspaces2022springspring-annotation-bookspring-annotationspring-annotation-chapter-20targetclassesiobinghespringannotationchapter20constructbeanConstructCircularBeanA.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'constructCircularBeanB' defined in file [D:Workspaces2022springspring-annotation-bookspring-annotationspring-annotation-chapter-20targetclassesiobinghespringannotationchapter20constructbeanConstructCircularBeanB.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'constructCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
/*************省略其他信息**************/
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'constructCircularBeanB' defined in file [D:Workspaces2022springspring-annotation-bookspring-annotationspring-annotation-chapter-20targetclassesiobinghespringannotationchapter20constructbeanConstructCircularBeanB.class]: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'constructCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
/*************省略其他信息**************/
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'constructCircularBeanA': Requested bean is currently in creation: Is there an unresolvable circular reference?

可以看到,Spring抛出了循环依赖的异常,说明Spring不支持基于构造方法的循环依赖。接下来,具体分析下Spring不支持基于构造方法的循环依赖的原因。具体分析步骤如下所示。

(1)解析AbstractBeanFactory类的doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)方法

源码详见:org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)。

此时重点关注如下代码片段。

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
    String beanName = transformedBeanName(name);
    Object beanInstance;
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
       /********省略其他代码**********/
    } else {
       /********省略其他代码**********/
            if (mbd.isSingleton()) {
                sharedInstance = getSingleton(beanName, () -> {
                    try {
                        return createBean(beanName, mbd, args);
                    }
                    catch (BeansException ex) {
                        /********省略其他代码**********/
                    }
                });
                beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
            }
   /********省略其他代码**********/
        }
        catch (BeansException ex) {
           /********省略其他代码**********/
        }
        finally {
            /********省略其他代码**********/
        }
    }
    return adaptBeanInstance(name, beanInstance, requiredType);
}

可以看到,在AbstractBeanFactory类的doGetBean()方法中,会调用getSingleton()方法。

(2)解析DefaultSingletonBeanRegistry类的getSingleton(String beanName, ObjectFactory<?> singletonFactory)方法

源码详见:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(String beanName, ObjectFactory<?> singletonFactory)。

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(beanName, "Bean name must not be null");
    synchronized (this.singletonObjects) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null) {
            if (this.singletonsCurrentlyInDestruction) {
                throw new BeanCreationNotAllowedException(beanName, "Singleton bean creation not allowed while singletons of this factory are in destruction " + "(Do not request a bean from a BeanFactory in a destroy method implementation!)");
            }
            if (logger.isDebugEnabled()) {
                logger.debug("Creating shared instance of singleton bean '" + beanName + "'");
            }
            beforeSingletonCreation(beanName);
            boolean newSingleton = false;
            boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
            if (recordSuppressedExceptions) {
                this.suppressedExceptions = new LinkedHashSet<>();
            }
            try {
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            }
            catch (IllegalStateException ex) {
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    throw ex;
                }
            }
            catch (BeanCreationException ex) {
                if (recordSuppressedExceptions) {
                    for (Exception suppressedException : this.suppressedExceptions) {
                        ex.addRelatedCause(suppressedException);
                    }
                }
                throw ex;
            }
            finally {
                if (recordSuppressedExceptions) {
                    this.suppressedExceptions = null;
                }
                afterSingletonCreation(beanName);
            }
            if (newSingleton) {
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}

可以看到,在getSingleton()方法创建Bean之前会调用beforeSingletonCreation()方法,在创建Bean之后会调用afterSingletonCreation()方法。

(3)解析DefaultSingletonBeanRegistry类的beforeSingletonCreation(String beanName)方法

源码详见:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#beforeSingletonCreation(String beanName)。

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

可以看到,在创建单例Bean之前,会将要创建的Bean的名称添加到singletonsCurrentlyInCreation中,添加失败,说明singletonsCurrentlyInCreation集合中已经存在当前Bean的名称,发生了循环依赖,就会抛出BeanCurrentlyInCreationException异常。那么singletonsCurrentlyInCreation又是个什么鬼呢?

singletonsCurrentlyInCreation的源码详见:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#singletonsCurrentlyInCreation。

private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

可以看到,singletonsCurrentlyInCreation其实就是一个Set集合。也就是说,Spring会在创建单例Bean之前,将正在创建的Bean的名称添加到singletonsCurrentlyInCreation集合中。

(4)解析DefaultSingletonBeanRegistry类的afterSingletonCreation(String beanName)方法

源码详见:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#afterSingletonCreation(String beanName)。

protected void afterSingletonCreation(String beanName) {
    if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
        throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
    }
}

可以看到,在afterSingletonCreation()方法中,主要是移除singletonsCurrentlyInCreation中对应的Bean的名称。

综合beforeSingletonCreation()和afterSingletonCreation()两个方法可以看出,Spring在创建单例Bean之前,会将Bean的名称存入singletonsCurrentlyInCreation集合中,实例化Bean之后,会将Bean的名称从singletonsCurrentlyInCreation集合中移除。并且,Spring在创建单例Bean之前,会调用beforeSingletonCreation()方法将要创建的Bean的名称添加到singletonsCurrentlyInCreation中,添加失败,说明singletonsCurrentlyInCreation集合中已经存在当前Bean的名称,发生了循环依赖,就会抛出BeanCurrentlyInCreationException异常。

5.5 不支持@DependsOn的循环依赖

运行4.5节中DependsOnCircularTest类的main()方法,输出的结果信息如下所示。

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dependsOnCircularBeanB' defined in file [D:Workspaces2022springspring-annotation-bookspring-annotationspring-annotation-chapter-20targetclassesiobinghespringannotationchapter20dependsonbeanDependsOnCircularBeanB.class]: Circular depends-on relationship between 'dependsOnCircularBeanB' and 'dependsOnCircularBeanA'

从输出的结果信息可以看出,Spring抛出了循环依赖的异常。说明Spring不支持@DependsOn注解的循环依赖。接下来,就分析下Spring不支持@DependsOn注解的循环依赖的原因。具体分析步骤如下所示。

解析AbstractBeanFactory类的doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)方法

源码详见:org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)。重点关注如下代码片段。

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
    String beanName = transformedBeanName(name);
    Object beanInstance;
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
         /*********省略其他代码*********/
    }
    else {
        /*********省略其他代码*********/
        try {
            /*********省略其他代码*********/
            String[] dependsOn = mbd.getDependsOn();
            if (dependsOn != null) {
                for (String dep : dependsOn) {
                    if (isDependent(beanName, dep)) {
                        throw new BeanCreationException(mbd.getResourceDescription(), beanName,  "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
                    }
                    registerDependentBean(dep, beanName);
                    try {
                        getBean(dep);
                    }
                    catch (NoSuchBeanDefinitionException ex) {
                        throw new BeanCreationException(mbd.getResourceDescription(), beanName,  "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
                    }
                }
            }
             /*********省略其他代码*********/
        }
        catch (BeansException ex) {
            /*********省略其他代码*********/
        }
        finally {
           /*********省略其他代码*********/

        }
    }
    return adaptBeanInstance(name, beanInstance, requiredType);
}

可以看到,在AbstractBeanFactory类的doGetBean(String name, @Nullable ClassrequiredType, @Nullable Object[] args, boolean typeCheckOnly)方法中,会判断是否存在@DependsOn注解的循环依赖,如果存在则抛出BeanCreationException异常。所以,Spring不支持@DependsOn注解的循环依赖。

5.6 支持单例Bean的setter循环依赖

运行4.6节中SingletonCircularTest类的main()方法,输出的结果信息如下所示。

singletnotallow===>>>io.binghe.spring.annotation.chapter20.singleton.bean.SingletonCircularBeanA@41e1e210
singletnotallow===>>>io.binghe.spring.annotation.chapter20.singleton.bean.SingletonCircularBeanB@be35cd9

可以看到,正确输出了SingletonCircularBeanA类型的Bean对象和SingletonCircularBeanB类型的Bean对象。说明Spring支持基于单例Bean的setter方法的循环依赖。接下来,就分析下Spring为何支持单例Bean的setter循环依赖。

(1)三级缓存

Spring使用了三级缓存来解决循环依赖的问题,三级缓存的源码详见:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry。

private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

其中,每个Map的含义如下所示。

  • singletonObjects:一级缓存,存储所有实例化,并且为属性赋值的单实例Bean。
  • earlySingletonObjects:二级缓存,存储实例化后还没来得及为属性赋值的单实例Bean。
  • singletonFactories:三级缓存,存储生产单实例Bean的工厂。

关于三级缓存,面试中经常会问如下两个问题,这里也给大家分享下。

  • Spring解决循环依赖为什么需要二级缓存?

二级缓存主要是为了分离创建出的完整的Bean和未对属性赋值的Bean,二级缓存中实际主要存储的是未对属性赋值的Bean,这样做的目的就是为了防止在多线程并发的场景中,读取到还未创建完成的Bean。所以,为了保证在多线程并发的环境中,读取到的Bean是完整的(已经为属性赋值),不会读取到未对属性赋值的Bean,需要使用二级缓存解决循环依赖。

另外,就一、二、三级缓存而言,二级缓存主要存储的是三级缓存创建出来的,并且未对属性赋值的Bean,这样做的目的也是为了防止三级缓存中的工厂类重复执行创建对象的逻辑。

  • Spring只用二级缓存能否解决循环依赖?为什么一定要用三级缓存来解决循环依赖呢?

其实,Spring使用二级缓存就完全能够解决循环依赖的问题,也可以支持Spring基于BeanPostProcessor的扩展能力。但是,由于Spring中的方法在设计上遵循了单一职责的原则,一个方法通常只做一件事情,getBean()方法就是获取Bean对象。但是,调用BeanPostProcessor创建动态代理是处于创建Bean的过程,如果在getBean()中实现这个逻辑,显然代码逻辑比较耦合。为了解决代码耦合的问题,保持方法的职责单一,方面后期维护。需要将创建动态代理的BeanPostProcessor放在创建Bean的方法中。并且将判断是否存在循环依赖的逻辑放在getSingleton()方法中。此时就需要三级缓存,在三级缓存中存放一个工厂接口,在接口的实现类中调用BeanPostProcessor创建动态代理对象。为了防止重复创建代理对象,将三级缓存中创建的代理对象存入二级缓存。在Spring中使用三级缓存完美解决了解耦、性能、扩展的问题。

(2)创建单例工厂

Spring在创建Bean对象时,会先创建一个和Bean的名称相同的单例工厂,并将Bean先放入单例工厂中。

源码详见:org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)。

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
 /**********省略其他代码***********/
    if (earlySingletonExposure) {
       /**********省略其他代码***********/
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
 /**********省略其他代码***********/
    return exposedObject;
}

在AbstractAutowireCapableBeanFactory类的doCreateBean()方法中调用了addSingletonFactory()方法。

addSingletonFactory()方法的源码详见:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory)。

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()方法的作用是将正在创建中的Bean的单例工厂,存放在三级缓存里,这样就保证了在循环依赖查找的时候是可以找到Bean的引用的。

(3)读取缓存数据

具体读取缓存获取Bean的过程在类DefaultSingletonBeanRegistry的getSingleton()方法中,源码详见:org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(String beanName, boolean allowEarlyReference)。

@Nullable
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) {
            synchronized (this.singletonObjects) {
                // 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();
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

通过上面的源码我们可以看到,在获取单例Bean的时候,会先从一级缓存singletonObjects里获取,如果没有获取到(说明不存在或没有实例化完成),会去第二级缓存earlySingletonObjects中去找,如果还是没有找到的话,就会三级缓存中获取单例工厂singletonFactory,通过从singletonFactory中获取正在创建中的引用,将singletonFactory存储在earlySingletonObjects 二级缓存中,这样就将创建中的单例引用从三级缓存中升级到了二级缓存中,二级缓存earlySingletonObjects,是会提前暴露已完成构造,还未执行属性注入的单例bean的。这个时候如何还有其他的bean也是需要属性注入,那么就可以直接从earlySingletonObjects中获取了。

注意:为了防止多线程并发环境下重复执行三级缓存中创建Bean的过程,在对singletonObjects加锁后,还会先从一级缓存singletonObjects中获取数据,如果数据不存在则从二级缓存earlySingletonObjects中获取数据,如果数据仍然不存在,才会从三级缓存singletonFactories中获取singletonFactory,调用singletonFactory的getObject()方法获取实例化但未对属性赋值的Bean对象,将其存入二级缓存,并且从三级缓存中移除对应的singletonFactory。

(4)解决循环依赖的完整流程图

最后给出Spring支持单例Bean的setter循环依赖的完整流程图,如图20-6所示。

万字长文带你彻底吃透Spring循环依赖

大家可以按照20-6的逻辑分析Spring解决循环依赖的代码,就相对比较清晰了。这里,就不再分析具体源码了。

六、总结

Spring的循环依赖问题介绍完了,我们一起总结下吧!

本章,主要详细分析了Spring的循环依赖问题,首先介绍了缓存依赖的基本概念和循环依赖的类型。随后以案例的形式详细介绍了循环依赖的场景,并详细分析了Spring循环依赖的底层解决方案。通过分析得知:Spring默认会支持单例Bean的setter循环依赖,对于其他情况下的循环依赖,Spring默认是不支持的。并且,最后给出了Spring解决循环依赖的流程图。

七、思考

既然学完了,就开始思考几个问题吧?

关于Spring的循环依赖,通常会有如下几个经典面试题:

  • 什么是循环依赖问题?
  • 循环依赖有哪些类型?
  • 在Spring中支持哪种循环依赖?
  • 列举几种Spring不支持的循环依赖的场景,为什么不支持?
  • Spring解决循环依赖的流程是什么?
  • Spring解决缓存依赖时,二级缓存的作用是什么?
  • Spring只用二级缓存能否解决循环依赖?为什么一定要用三级缓存来解决循环依赖呢?
  • 你从Spring解决循环依赖的设计中得到了哪些启发?


Tags:Spring   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
Spring Security:保障应用安全的利器
SpringSecurity作为一个功能强大的安全框架,为Java应用程序提供了全面的安全保障,包括认证、授权、防护和集成等方面。本文将介绍SpringSecurity在这些方面的特性和优势,以及它...【详细内容】
2024-02-27  Search: Spring  点击:(52)  评论:(0)  加入收藏
Spring Security权限控制框架使用指南
在常用的后台管理系统中,通常都会有访问权限控制的需求,用于限制不同人员对于接口的访问能力,如果用户不具备指定的权限,则不能访问某些接口。本文将用 waynboot-mall 项目举例...【详细内容】
2024-02-19  Search: Spring  点击:(39)  评论:(0)  加入收藏
详解基于SpringBoot的WebSocket应用开发
在现代Web应用中,实时交互和数据推送的需求日益增长。WebSocket协议作为一种全双工通信协议,允许服务端与客户端之间建立持久性的连接,实现实时、双向的数据传输,极大地提升了用...【详细内容】
2024-01-30  Search: Spring  点击:(8)  评论:(0)  加入收藏
Spring实现Kafka重试Topic,真的太香了
概述Kafka的强大功能之一是每个分区都有一个Consumer的偏移值。该偏移值是消费者将读取的下一条消息的值。可以自动或手动增加该值。如果我们由于错误而无法处理消息并想重...【详细内容】
2024-01-26  Search: Spring  点击:(84)  评论:(0)  加入收藏
SpringBoot如何实现缓存预热?
缓存预热是指在 Spring Boot 项目启动时,预先将数据加载到缓存系统(如 Redis)中的一种机制。那么问题来了,在 Spring Boot 项目启动之后,在什么时候?在哪里可以将数据加载到缓存系...【详细内容】
2024-01-19  Search: Spring  点击:(86)  评论:(0)  加入收藏
Spring Boot2.0深度实践 核心原理拆解+源码分析
Spring Boot2.0深度实践:核心原理拆解与源码分析一、引言Spring Boot是一个基于Java的轻量级框架,它简化了Spring应用程序的创建过程,使得开发者能够快速搭建一个可运行的应用...【详细内容】
2024-01-15  Search: Spring  点击:(93)  评论:(0)  加入收藏
SpringBoot3+Vue3 开发高并发秒杀抢购系统
开发高并发秒杀抢购系统:使用SpringBoot3+Vue3的实践之旅随着互联网技术的发展,电商行业对秒杀抢购系统的需求越来越高。为了满足这种高并发、高流量的场景,我们决定使用Spring...【详细内容】
2024-01-14  Search: Spring  点击:(90)  评论:(0)  加入收藏
Spring Boot 3.0是什么?
Spring Boot 3.0是一款基于Java的开源框架,用于简化Spring应用程序的构建和开发过程。与之前的版本相比,Spring Boot 3.0在多个方面进行了改进和增强,使其更加易用、高效和灵活...【详细内容】
2024-01-11  Search: Spring  点击:(130)  评论:(0)  加入收藏
GraalVM与Spring Boot 3.0:加速应用性能的完美融合
在2023年,SpringBoot3.0的发布标志着Spring框架对GraalVM的全面支持,这一支持是对Spring技术栈的重要补充。GraalVM是一个高性能的多语言虚拟机,它提供了Ahead-of-Time(AOT)编...【详细内容】
2024-01-11  Search: Spring  点击:(124)  评论:(0)  加入收藏
Spring Boot虚拟线程的性能还不如Webflux?
早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章,觉得还不错。内容较长,抓重点给大家介绍一下这篇文章的核心内容,方便大家快速阅读。测试场景作者采用了一个尽可...【详细内容】
2024-01-10  Search: Spring  点击:(115)  评论:(0)  加入收藏
▌简易百科推荐
对于微服务架构监控应该遵守的原则
随着软件交付方式的变革,微服务架构的兴起使得软件开发变得更加快速和灵活。在这种情况下,监控系统成为了微服务控制系统的核心组成部分。随着软件的复杂性不断增加,了解系统的...【详细内容】
2024-04-03  步步运维步步坑    Tags:架构   点击:(4)  评论:(0)  加入收藏
大模型应用的 10 种架构模式
作者 | 曹洪伟在塑造新领域的过程中,我们往往依赖于一些经过实践验证的策略、方法和模式。这种观念对于软件工程领域的专业人士来说,已经司空见惯,设计模式已成为程序员们的重...【详细内容】
2024-03-27    InfoQ  Tags:架构模式   点击:(13)  评论:(0)  加入收藏
哈啰云原生架构落地实践
一、弹性伸缩技术实践1.全网容器化后一线研发的使用问题全网容器化后一线研发会面临一系列使用问题,包括时机、容量、效率和成本问题,弹性伸缩是云原生容器化后的必然技术选择...【详细内容】
2024-03-27  哈啰技术  微信公众号  Tags:架构   点击:(10)  评论:(0)  加入收藏
DDD 与 CQRS 才是黄金组合
在日常工作中,你是否也遇到过下面几种情况: 使用一个已有接口进行业务开发,上线后出现严重的性能问题,被老板当众质疑:“你为什么不使用缓存接口,这个接口全部走数据库,这怎么能扛...【详细内容】
2024-03-27  dbaplus社群    Tags:DDD   点击:(11)  评论:(0)  加入收藏
高并发架构设计(三大利器:缓存、限流和降级)
软件系统有三个追求:高性能、高并发、高可用,俗称三高。本篇讨论高并发,从高并发是什么到高并发应对的策略、缓存、限流、降级等。引言1.高并发背景互联网行业迅速发展,用户量剧...【详细内容】
2024-03-13    阿里云开发者  Tags:高并发   点击:(5)  评论:(0)  加入收藏
如何判断架构设计的优劣?
架构设计的基本准则是非常重要的,它们指导着我们如何构建可靠、可维护、可测试的系统。下面是这些准则的转换表达方式:简单即美(KISS):KISS原则的核心思想是保持简单。在设计系统...【详细内容】
2024-02-20  二进制跳动  微信公众号  Tags:架构设计   点击:(36)  评论:(0)  加入收藏
详解基于SpringBoot的WebSocket应用开发
在现代Web应用中,实时交互和数据推送的需求日益增长。WebSocket协议作为一种全双工通信协议,允许服务端与客户端之间建立持久性的连接,实现实时、双向的数据传输,极大地提升了用...【详细内容】
2024-01-30  ijunfu  今日头条  Tags:SpringBoot   点击:(8)  评论:(0)  加入收藏
PHP+Go 开发仿简书,实战高并发高可用微服务架构
来百度APP畅享高清图片//下栽のke:chaoxingit.com/2105/PHP和Go语言结合,可以开发出高效且稳定的仿简书应用。在实现高并发和高可用微服务架构时,我们可以采用一些关键技术。首...【详细内容】
2024-01-14  547蓝色星球    Tags:架构   点击:(114)  评论:(0)  加入收藏
GraalVM与Spring Boot 3.0:加速应用性能的完美融合
在2023年,SpringBoot3.0的发布标志着Spring框架对GraalVM的全面支持,这一支持是对Spring技术栈的重要补充。GraalVM是一个高性能的多语言虚拟机,它提供了Ahead-of-Time(AOT)编...【详细内容】
2024-01-11    王建立  Tags:Spring Boot   点击:(124)  评论:(0)  加入收藏
Spring Boot虚拟线程的性能还不如Webflux?
早上看到一篇关于Spring Boot虚拟线程和Webflux性能对比的文章,觉得还不错。内容较长,抓重点给大家介绍一下这篇文章的核心内容,方便大家快速阅读。测试场景作者采用了一个尽可...【详细内容】
2024-01-10  互联网架构小马哥    Tags:Spring Boot   点击:(115)  评论:(0)  加入收藏
站内最新
站内热门
站内头条