Spring IoC之循环依赖处理

简介: Spring IoC之循环依赖处理

什么是循环依赖


循环依赖其实是循环引用,也就是两个或则两个以上的 bean 互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。如下图所示:

image.png


注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。

Spring 中循环依赖场景有:

  • 构造器的循环依赖
  • field 属性的循环依赖


对于构造器的循环依赖,Spring 是无法解决的,只能抛出 BeanCurrentlyInCreationException 异常表示循环依赖,所以下面我们分析的都是基于 field 属性的循环依赖。


Spring 只解决 scope 为 singleton 的循环依赖,对于scope 为 prototype 的 bean Spring 无法解决,直接抛出 BeanCurrentlyInCreationException 异常。


如何检测循环依赖


检测循环依赖相对比较容易,Bean 在创建的时候可以给该 Bean 做标记,如果递归调用回来发现正在创建中的话,即说明了循环依赖了。


解决循环依赖


我们先从加载 bean 最初始的方法 doGetBean() 开始,该方法位于 AbstractBeanFactory 类中。


doGetBean() 中,首先通过 transformedBeanName(name)获取 beanName,然后调用 DefaultSingletonBeanRegistry 类中 getSingleton()方法,该方法会根据 beanName 从单例 bean 缓存中获取,如果不为空则直接返回。


protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
        synchronized(this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}
复制代码


这个方法主要是从三个缓存中获取,分别是:singletonObjects、earlySingletonObjects、singletonFactories,三者定义如下:


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 HashMap<>(16);
复制代码


它们就是 Spring 解决 singleton bean 的关键因素所在,被称为三级缓存,第一级为 singletonObjects,第二级为 earlySingletonObjects,第三级为 singletonFactories。这里我们可以通过 getSingleton()  看到他们是如何配合的,在分析该方法之前,提下其中的  isSingletonCurrentlyInCreation()allowEarlyReference


  • isSingletonCurrentlyInCreation():判断当前 singleton bean 是否处于创建中。即是否包含在 singletonsCurrentlyInCreation 中。bean 处于创建中也就是说 bean 在初始化但是没有完成初始化,有一个这样的过程其实和 Spring 解决 bean 循环依赖的理念相辅相成,因为 Spring 解决 singleton bean 的核心就在于提前曝光 bean。
  • allowEarlyReference:从字面意思上理解就是允许提前拿到引用。其实真正的意思是是否允许从 singletonFactories 缓存中通过 getObject()拿到对象,为什么会有这样一个字段呢?原因在于 singletonFactories 才是 Spring 解决 singleton bean 的诀窍所在,这个我们后续分析。


getSingleton() 整个过程如下: 首先从一级缓存 singletonObjects  中获取,如果没有且当前指定的 beanName 正在创建,就再从二级缓存中 earlySingletonObjects  获取,如果还是没有获取到且 allowEarlyReference 为 true,则从三级缓存 singletonFactories 中获取指定 beanName 对应的 singletonFactory,如果获取到则通过 singletonFactory#  getObject()  获取对象,并将其加入到二级缓存 earlySingletonObjects  中,然后从三级缓存 singletonFactories 中删除获取到的 singletonFactory。如下:


singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
复制代码


这样就从三级缓存升级到二级缓存了。


上面是从缓存中获取,但是缓存中的数据从哪里添加进来的呢?继续查看 doGetBean()中的代码 一直往下跟会发现有这么一段代码:


if (mbd.isSingleton()) {
    sharedInstance = this.getSingleton(beanName, () -> {
        try {
            return this.createBean(beanName, mbd, args);
        } catch (BeansException var5) {
            this.destroySingleton(beanName);
            throw var5;
        }
    });
    bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
复制代码


其中 createBean()就是用来创建 bean 的,其详细定义在 AbstractAutowireCapableBeanFactory 类中,我们主要为了查询缓存中的数据是怎么添加进来的,所以这里对于代码不进行过多的分析,后续章节会对其进行讲述。在 createBean()方法中有个  doCreateBean() 方法,当中有这么一段代码:


boolean earlySingletonExposure = mbd.isSingleton() && this.allowCircularReferences && this.isSingletonCurrentlyInCreation(beanName);
if (earlySingletonExposure) {
    if (this.logger.isTraceEnabled()) {
        this.logger.trace("Eagerly caching bean '" + beanName + "' to allow for resolving potential circular references");
    }
    this.addSingletonFactory(beanName, () -> {
        return this.getEarlyBeanReference(beanName, mbd, bean);
    });
}
复制代码


如果 earlySingletonExposure == true 的话,则调用 addSingletonFactory() 将他们添加到缓存中,但是一个 bean 要具备如下条件才会添加至缓存中:


  • 单例
  • 运行提前暴露 bean
  • 当前 bean 正在创建中

其中 allowCircularReferences 属性用来设置是否在 bean 之间允许循环引用,并自动尝试解决它们,默认值为 true。


addSingletonFactory() 代码如下:

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);
        }
    }
}
复制代码


从这段代码我们可以看出 singletonFactories 这个三级缓存才是解决 Spring Bean 循环依赖的诀窍所在。同时这段代码发生在 createBeanInstance() 方法之后,也就是说这个 bean 其实已经被创建出来了,但是它还不是很完美(没有进行属性填充和初始化),但是对于其他依赖它的对象而言已经足够了(可以根据对象引用定位到堆中对象),能够被认出来了,所以 Spring 在这个时候选择将该对象提前曝光出来让大家认识认识。


该段代码添加 singletonFactory 到三级缓存中时,且会删除二级缓存中 beanName 对应的内容,这个逻辑不是很清楚,在测试案例中进行调试的时候也没发现什么端倪,如果有大神了解这部分的逻辑,望不吝赐教。


介绍到这里我们发现三级缓存 singletonFactories 和 二级缓存 earlySingletonObjects 中的值都有出处了,那一级缓存在哪里设置的呢?在类 DefaultSingletonBeanRegistry 中可以发现这个 addSingleton() 方法,源码如下:


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 的链路中有哪个地方引用呢?在 doGetBean() 处理不同 scope 时,如果是 singleton,则调用 getSingleton(),如下:  


if (mbd.isSingleton()) {
    sharedInstance = this.getSingleton(beanName, () -> {
        try {
            return this.createBean(beanName, mbd, args);
        } catch (BeansException var5) {
            this.destroySingleton(beanName);
            throw var5;
        }
    });
    bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
复制代码
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) {
            //....
            try {
                singletonObject = singletonFactory.getObject();
                newSingleton = true;
            }
            //.....
            if (newSingleton) {
                addSingleton(beanName, singletonObject);
            }
        }
        return singletonObject;
    }
}
复制代码


至此,Spring 关于 singleton bean 循环依赖已经分析完毕了。所以我们基本上可以确定 Spring 解决循环依赖的方案了:Spring 在创建 bean 的时候并不是等它完全完成,而是在创建过程中将创建中的 bean 的 ObjectFactory 提前曝光(即加入到 singletonFactories 缓存中),这样一旦下一个 bean 创建的时候需要依赖 bean ,则直接使用 ObjectFactory 的 getObject() 获取了,也就是 getSingleton()中的代码片段了。


实例分析


新建三个 bean 类,相互之间构成引用关系。


Person类


public class Person {
    private String name;
    private AbstractCar car;
    private String desc;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public AbstractCar getCar() {
        return car;
    }
    public void setCar(AbstractCar car) {
        this.car = car;
    }
    public String getDesc() {
        return desc;
    }
    public void setDesc(String desc) {
        this.desc = desc;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", 拥有一辆car=" + car +
                '}';
    }
}
复制代码


AbstractCar 类


public class AbstractCar {
    private String brand ;
    private Money money;
    public String getBrand() {
        return brand;
    }
    public void setBrand(String brand) {
        this.brand = brand;
    }
    public Money getMoney() {
        return money;
    }
    public void setMoney(Money money) {
        this.money = money;
    }
    @Override
    public String toString() {
        return "AbstractCar{" +
                "brand='" + brand + '\'' +
                ", money=" + money +
                '}';
    }
}
复制代码


Money 类


public class Money {
    private String classification;
    private Person person;
    public String getClassification() {
        return classification;
    }
    public void setClassification(String classification) {
        this.classification = classification;
    }
    public Person getPerson() {
        return person;
    }
    public void setPerson(Person person) {
        this.person = person;
    }
    @Override
    public String toString() {
        return "Money{" +
                "classification='" + classification + '\'' +
                ", person=" + person.getName() +
                '}';
    }
}
复制代码


beans.xml 文件


<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="car" class="com.msdn.bean.AbstractCar"  p:brand="宝马" p:money-ref="money" />
    <bean id="person" class="com.msdn.bean.Person" p:name="herish" p:car-ref="car" />
    <bean id="money" class="com.msdn.bean.Money" p:classification="工资" p:person-ref="person" />
</beans>
复制代码


测试代码


@Test
public void cycleRely(){
    ClassPathResource resource = new ClassPathResource("config/beans.xml");
    BeanFactory beanFactory = new XmlBeanFactory(resource);
    Person person = (Person) beanFactory.getBean("person");
    System.out.println(person);
}
复制代码


运行结果为:


Person{name='herish', 拥有一辆car=AbstractCar{brand='宝马', money=Money{classification='工资', person=herish}}}
复制代码


分析


关于上述代码用图解的形式表示如下:


image.png


对测试代码进行调试,大概了解其中的逻辑跳转。首先是 beanFactory.getBean("person"),该方法最终会定位在 doGetBean("person"),执行 getSingleton(beanName, allowEarlyReference)返回结果为 null,继续向下执行,如果 scope 为 单例的情况下,执行 createBean(beanName, mbd, args)方法,具体实现在 AbstractAutowireCapableBeanFactory 类中,然后调用 doCreateBean(),在该方法中,createBeanInstance()方法会先初始化对象赋零值,然后在 addSingletonFactory()中将”person“添加到三级缓存中,接着在 populateBean()方法中给对象添加属性内容,该部分的关键代码为 applyPropertyValues()方法,当发现有一个 Car 的引用属性,然后在 BeanDefinitionValueResolver 类中的 resolveValueIfNecessary()方法内跳转到 resolveReference(),进而调用 doGetBean("car")方法。


同上述过程一致,进而调用 doGetBean("money")方法,因为 Monery 类中有对 Person 的引用,所以最后又会执行 doGetBean("person")方法。关于这点就像递归调用一样,我们假设最初的 doGetBean("person")为第一层,则最后的为第四层,我们接下来要做的就是不断返回到上一层。此时 beanFactory 关于缓存的结果为:


image.png


再次进入到 getSingleton(beanName, allowEarlyReference)方法时,二级缓存中会添加”person“,三级缓存并删除它。接着会执行 getObjectForBeanInstance()方法,然后返回 person 对象。


在 DefaultSingletonBeanRegistry 类中的 registerDependentBean()方法会记录 bean 之间的引用关系,然后返回第三层,在第三层的 populateBean()执行完毕之后,最后 doCreateBean()方法会返回这样的结果:


image.png


createBean()执行结束后,接着会执行 getSingleton(beanName,singletonFactory),该方法会将 money 对象加入到一级缓存中。在返回 money 对象后,返回第二层。第二层返回结果为:

image.png


将 car 对象加入到一级缓存之后,来到了第一层。第一层返回结果为:


image.png


然后将 person 对象加入到一级缓存中,再把 singletonsCurrentlyInCreation 清空。最后得到的 beanFactory 结果为:


image.png


到这里,关于 Spring 解决 bean 循环依赖就已经分析完毕了。最后来描述下就上面那个循环依赖 Spring 解决的过程(A为person,B为car,C为money):首先 A 完成初始化第一步并将自己提前曝光出来(通过 ObjectFactory 将自己提前曝光),在初始化的时候,发现自己依赖对象 B,此时就会去尝试 get(B),这个时候发现 B 还没有被创建出来,然后 B 就走创建流程,在 B 初始化的时候,同样发现自己依赖 C,C 也没有被创建出来,这个时候 C 又开始初始化进程,但是在初始化的过程中发现自己依赖 A,于是尝试 get(A),这个时候由于 A 已经添加至缓存中(一般都是添加至三级缓存 singletonFactories ),通过 ObjectFactory 提前曝光,所以可以通过 ObjectFactory.getObject() 拿到 A 对象,C 拿到 A 对象后顺利完成初始化,然后将自己添加到一级缓存中,回到 B ,B 也可以拿到 C 对象,完成初始化,A 可以顺利拿到 B 完成初始化。到这里整个链路就已经完成了初始化过程了。


备注:测试案例也尝试了将 bean 之间的关系设置为依赖关系,但是执行结果报错。


参考文献


https://www.cnblogs.com/java-chen-hao/p/11139887.html



目录
相关文章
|
7月前
|
XML Java 测试技术
《深入理解Spring》:IoC容器核心原理与实战
Spring IoC通过控制反转与依赖注入实现对象间的解耦,由容器统一管理Bean的生命周期与依赖关系。支持XML、注解和Java配置三种方式,结合作用域、条件化配置与循环依赖处理等机制,提升应用的可维护性与可测试性,是现代Java开发的核心基石。
|
XML Java 测试技术
Spring IOC—基于注解配置和管理Bean 万字详解(通俗易懂)
Spring 第三节 IOC——基于注解配置和管理Bean 万字详解!
991 26
|
监控 安全 Java
解决 Spring Boot 中 SecurityConfig 循环依赖问题的详解
本文详细解析了在 Spring Boot 中配置 `SecurityConfig` 时可能遇到的循环依赖问题。通过分析错误日志与代码,指出问题根源在于 `SecurityConfig` 类中不当的依赖注入方式。文章提供了多种解决方案:移除 `configureGlobal` 方法、定义 `DaoAuthenticationProvider` Bean、使用构造函数注入以及分离配置类等。此外,还讨论了 `@Lazy` 注解和允许循环引用的临时手段,并强调重构以避免循环依赖的重要性。通过合理设计 Bean 依赖关系,可确保应用稳定启动并提升代码可维护性。
961 0
|
Java Maven 微服务
微服务——SpringBoot使用归纳——Spring Boot集成 Swagger2 展现在线接口文档——Swagger2 的 maven 依赖
在项目中使用Swagger2工具时,需导入Maven依赖。尽管官方最高版本为2.8.0,但其展示效果不够理想且稳定性欠佳。实际开发中常用2.2.2版本,因其稳定且界面友好。以下是围绕2.2.2版本的Maven依赖配置,包括`springfox-swagger2`和`springfox-swagger-ui`两个模块。
598 0
|
11月前
|
XML 人工智能 Java
Spring IOC 到底是什么?
IOC(控制反转)是一种设计思想,主要用于解耦代码,简化依赖管理。其核心是将对象的创建和管理交给容器处理,而非由程序直接硬编码实现。通过IOC,开发者无需手动new对象,而是由框架负责实例化、装配和管理依赖对象。常见应用如Spring框架中的BeanFactory和ApplicationContext,它们实现了依赖注入和动态管理功能,提升了代码的灵活性与可维护性。
261 1
|
XML Java 数据格式
Spring IoC容器的设计与实现
Spring 是一个功能强大且模块化的 Java 开发框架,其核心架构围绕 IoC 容器、AOP、数据访问与集成、Web 层支持等展开。其中,`BeanFactory` 和 `ApplicationContext` 是 Spring 容器的核心组件,分别定位为基础容器和高级容器,前者提供轻量级的 Bean 管理,后者扩展了事件发布、国际化等功能。
327 18
|
XML Java 数据格式
京东一面:spring ioc容器本质是什么? ioc容器启动的步骤有哪些?
京东一面:spring ioc容器本质是什么? ioc容器启动的步骤有哪些?
|
XML Java 数据格式
【SpringFramework】Spring IoC-基于XML的实现
本文主要讲解SpringFramework中IoC和DI相关概念,及基于XML的实现方式。
328 69
|
Java 容器 Spring
什么是Spring IOC 和DI ?
IOC : 控制翻转 , 它把传统上由程序代码直接操控的对象的调用权交给容 器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转 移,从程序代码本身转移到了外部容器。 DI : 依赖注入,在我们创建对象的过程中,把对象依赖的属性注入到我们的类中。
|
Java Spring 容器
【SpringFramework】Spring IoC-基于注解的实现
本文主要记录基于Spring注解实现IoC容器和DI相关知识。
263 21