专栏首页码猿技术专栏Spring解决循环依赖

Spring解决循环依赖

文章目录

1. 什么是循环依赖

1.1. 属性的相互依赖

1.2. 构造器的依赖

2. 如何解决

2.1. 三级缓存

2.2. 流程分析

2.3. 结合上述例子分析

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

4. Spring为什么不能解决多例的循环依赖

什么是循环依赖

  • 循环依赖分为两种,一种是构造器的相互依赖,另外一种是属性的相互依赖。

属性的相互依赖

  • 属性的相互依赖就是A类的属性中有B,B类的属性中有A,如下:
    • 这里仅仅是两个类的循环依赖,当然还有更多的类相互依赖
public class A{
    private B b;
}

public class B{
    private A a;
}

构造器的依赖

  • 构造器的依赖就是A类的构造器需要传入B类对象,B的构造器需要传入A类对象,如下:
public class A{
    public A(B b){}
}

public class B{
    private B(A a){}
}

如何解决

  • Spring只能解决属性的循环依赖,构造器的循环依赖是不能解决的。
  • spring中解决循环依赖的核心思想就是利用三级缓存,先创建Bean,后为各个属性赋值具体什么是三级缓存呢?

三级缓存

  • 三级缓存的实现在org.springframework.beans.factory.support.DefaultSingletonBeanRegistry类中,如下:
    • private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);:单例对象缓存池,beanName->Bean,其中存储的就是实例化,属性赋值成功之后的单例对象
    • private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);:早期的单例对象,beanName->Bean,其中存储的是实例化之后,属性未赋值的单例对象。
    • private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);:单例工厂的缓存,beanName->ObjectFactory

流程分析

  • 在分析之前,先写个实例,制造循环依赖,如下:
@Component
public class Husband {
    private String name;
    private Integer age;
    //丈夫依赖妻子
    @Autowired
    private Wife wife;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Wife getWife() {
        return wife;
    }

    public void setWife(Wife wife) {
        this.wife = wife;
    }
}



@Component
public class Wife {
    private String name;
    private Integer age;
    //妻子依赖丈夫
    @Autowired
    private Husband husband;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Husband getHusband() {
        return husband;
    }

    public void setHusband(Husband husband) {
        this.husband = husband;
    }
}
  • 解决循环依赖是在容器启动的时候实现的,我们分析源码可以知道,容器启动初始化的大部分过程都是在refresh方法中实现的,因此我们从refresh入口即可,流程如下:
    • 下面只分析与循环依赖有关的流程,其他的会省略
1、finishBeanFactoryInitialization(beanFactory):实例化所有饿加载的单例Bean

2、org.springframework.beans.factory.config.ConfigurableListableBeanFactory#preInstantiateSingletons:真正的实现的方法

3、org.springframework.beans.factory.support.AbstractBeanFactory#getBean:调用方法获取Bean

4、org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean:真正getBean的方法
	4.1 Object sharedInstance = getSingleton(beanName):先尝试从三级缓存中获取Bean,第一次执行缓存中肯定是没有
	4.2 BeanFactory parentBeanFactory = getParentBeanFactory():检查父工厂中是否存在当前Bean
	4.3 markBeanAsCreated(beanName):将Bean放入alreadyCreated中,表示当前Bean已经创建了
	4.4 String[] dependsOn = mbd.getDependsOn():检查dependOn
	4.5 Object bean = resolveBeforeInstantiation(beanName, mbdToUse):执行后置处理器,在初始化之前执行
	4.6 sharedInstance = getSingleton(beanName, () -> {:调用getSingleton方法,尝试从单例缓存池中获取Bean
		4.6.1 Object singletonObject = this.singletonObjects.get(beanName):从单例缓存池中获取
		4.6.2 if (singletonObject == null) {:如果缓存池中没有,那么执行后续的逻辑,如果有直接返回即可
		4.6.3 单例池中没有的逻辑:singletonObject = singletonFactory.getObject():调用createBean方法创建Bean
		4.6.4 addSingleton(beanName, singletonObject):创建Bean成功之后将其添加到单例缓存池中,并且将其从二三级缓存中移除(singletonFactories,earlySingletonObjects)
		
	4.7 return createBean(beanName, mbd, args):三级缓存和父工厂中都没有当前的这个Bean,此时进入创建Bean的过程
	
5、org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean:真正执行创建Bean的逻辑
	5.1 instanceWrapper = createBeanInstance(beanName, mbd, args):调用构造器创建Bean,此时的Bean称为早期Bean,还未进行属性赋值
	5.2 applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName):执行MergedBeanDefinitionPostProcessor,也是一种后置处理器
	5.3 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)):将早期Bean放入三级缓存singletonFactories中,并且从早期对象缓存earlySingletonObjects中删除,用来解决循环依赖。
	if (!this.singletonObjects.containsKey(beanName)) {
				this.singletonFactories.put(beanName, singletonFactory);
				this.earlySingletonObjects.remove(beanName);
				this.registeredSingletons.add(beanName);
			}
	5.4 populateBean(beanName, mbd, instanceWrapper):此时执行当前Bean的属性赋值
	
6、org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean:进行属性的赋值
	6.1 if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) :执行实例化之后的后置处理器
	6.2 for (BeanPostProcessor bp : getBeanPostProcessors()) :执行属性赋值之前的后置处理器,返回PropertyVlaue,我们知道@Autowired注解就是后置处理器实现的,主要的逻辑就是在postProcessProperties方法中,并且我们的例子中是自动注入Wife和Husband的,所以逻辑一定是在这里实现的,进入

7、org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties:@Autowired注解解析,为标注该注解的属性赋值
	7.1 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject:真正执行赋值的逻辑
		7.1.1 value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter):解析依赖

8、org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency:解析标注@Autowired的依赖
	8.1 org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency:真正解析逻辑
	8.2 instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this):从容器中获取指定的Bean,内部调用的又是getBean方法

9、org.springframework.beans.factory.config.DependencyDescriptor#resolveCandidate:至此,又回到了getBean这个方法,从ioc容器中获取指定的Bean
  • 流程图如下:
    • 红色是关于三级缓存的操作
    • 蓝色是关于后置处理器的操作
    • 唯一一个淡黄色是解决循环依赖的关键,重新调用doGetBean

结合上述例子分析

1、Husband创建Bean,先判断缓存池中是否存在,存在直接返回,不存在进入createBean创建的流程,调用构造方法创建一个早期的Bean【未进行属性赋值】,创建成功将其放入二级缓存earlySingletonObjects中,之后又调用addSingletonFactory方法将其放入三级缓存中并且将二级缓存中的移除,之后调用populateBean为属性赋值,在@Autowired的后置处理器中查找需要注入的依赖,发现Husband中的一个属性Wife,因此调用getBean方法从容器中获取,但是此时的Wife还未创建,因此又进入了doGetBean的流程,但是此时Wife并没有创建,因此在一二三级缓存中不能获取,又执行createBean方法创建Wife,同样调用构造方法创建一个早期Bean放入二级缓存中,调用addSingletonFactory放入三级缓存并移除二级缓存,然后调用populateBean方法为Wife属性赋值,在@Autowired的后置处理器中查找需要注入的依赖,发现Wife类中有一个属性是Husband,因此调用getBean方法,再次调用doGetBean获取Husband,但是此时的Husband已经创建成功【未赋值】,存放在三级缓存中,因此直接从三级缓存中取出Husband赋值给Wife属性,至此Wife属性已经赋值成功,直接添加到一级缓存(singletonObjects)中并且移除三级缓存,直接返回给Husband赋值,因此Husband中的属性也持有了Wife的引用,都创建并且赋值成功了。

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

  • Spring解决循环依赖主要是依赖三级缓存,但是的在调用构造方法之前还未将其放入三级缓存之中,因此后续的依赖调用构造方法的时候并不能从三级缓存中获取到依赖的Bean,因此不能解决。

Spring为什么不能解决多例的循环依赖

  • 多实例Bean是每次调用一次getBean都会执行一次构造方法并且未属性赋值,根本没有三级缓存,因此解决循环依赖。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Spring生命周期

    主要的作用就是添加了一个后置处理器ServletContextAwareProcessor

    爱撒谎的男孩
  • FactoryBean解析

    爱撒谎的男孩
  • 设计模式之建造模式

    爱撒谎的男孩
  • 【asp.net core 系列】13 Identity 身份验证入门

    通过前两篇我们实现了如何在Service层如何访问数据,以及如何运用简单的加密算法对数据加密。这一篇我们将探索如何实现asp.net core的身份验证。

    程序员小高
  • 【每日一题】问题 1255: 能量项链

    在Mars星球上,每个Mars人都随身佩带着一串能量项链。在项链上有 N颗能量珠。能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数。并且,对于相邻的...

    编程范 源代码公司
  • prefetch错误https

    查看sra-tools版本是否最新版 可以重新安装最新版或建立新的环境安装最新,建议删除。

    Y大宽
  • [PySimpleGUI界面学习](六)一个文件浏览对话框

    <!--more--> # 回顾 在前几篇文章中,我们分析了用 `PySimpleGUI` 这个工具包来创建界面的基本方法,并且探讨了一些具体的细节问题,如果...

    王荣胜
  • 变量类型、构造器、封装以及 LeetCode 每日一题

    当系统加载类或创建该类的实例时,系统自动为成员变量分配内存空间,并在分配空间后,自动为成员变量赋初值。

    Carlos Ouyang
  • OLEDB adapter for Biztalk server 2006更新[源码]

    前一个阶段做一个<<General Database Adapter for Biztalk Server 2006 介绍>>主要功能实现对OLEDB数据库的访...

    阿新
  • VIM之个性化配置 .vimrc

    贴一个自己常用的vim配置文件,个人感觉这些配置不算臃肿,对于运维已经基本够用了。

    二狗不要跑

扫码关注云+社区

领取腾讯云代金券