理解spring循环依赖的处理⭐

spring循环依赖的处理⭐

先从单例模式讨论

Spring创建Bean: Spring的单例是基于BeanFactory也就是Spring容器的,单例Bean在此容器整个生命周期内只有一个。这时候,只有在第一次获取bean时才会初始化bean,即第一次请求该bean时才初始化。 每次获取到的对象都是同一个对象。

一、Spring循环依赖在创建 Bean 过程中,使用到了三级缓存

1、什么是循环依赖

情况:

  1. 自己依赖自己的直接依赖
  2. 两个对象之间的直接依赖
  3. 多个对象之间的间接依赖

循环依赖最常见场景:单例的setter注入

2、Spring 循环依赖为何使用三级缓存?

Spring 解决循环依赖的核心思想在于提前曝光:

  • 在构造 Bean 对象之后,将对象提前曝光到缓存中,这时候曝光的对象仅仅是构造完成,还没注入属性和初始化。
  • 提前曝光的对象被放入 Map<String, ObjectFactory<?>> singletonFactories 缓存中,这里并不是直接将 Bean 放入缓存,而是包装成 ObjectFactory 对象再放入

spring内部有三级缓存:

  • singletonObjects 一级缓存,用于保存实例化、注入、初始化完成的bean实例。也就是存放可用的成品Bean
  • earlySingletonObjects 二级缓存,用于保存实例化完成的bean实例。半成品Bean,已创建对象,但是未注入属性和初始化。用以解决循环依赖。
  • singletonFactories 三级缓存,用于保存bean创建工厂,用来生成半成品Bean并放入到二级缓存。以便于后面扩展有机会创建代理对象。

了解解决循环依赖的核心思想和spring内部有三级缓存,接下来就可以知道Spring中如何创建Bean。

Spring中如何创建Bean:

源码:

    /** 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);

使用到了三级缓存,即 DefaultSingletonBeanRegistry.java 中定义的。以 com.gyh.general 包下的 OneBean 为例,debug springboot 启动过程,分析 spring 是如何创建 bean 的。

参考源码中 spring 创建 bean 的过程。其中最关键的几步有:

1.getSingleton(beanName, true) 依次从一二三级缓存中查找 bean 对象,如果缓存中存在对象,则直接返回(early);

2.createBeanInstance(beanName, mbd, args) 选一个合适的构造函数,new 实例对象(instance),此时的 instance 中依赖的属性还都是 null,属于半成品;

3.singletonFactories.put(beanName, oneSingletonFactory) 利用上一步的 instance,构建一个 singletonFactory,并将其放到三级缓存中;

4.populateBean(beanName, mbd, instanceWrapper) 填充 bean:为该 bean 定义的属性创建对象或赋值;

5.initializeBean("one",oneInstance, mbd) 初始化 bean:对 bean 进行初始化或其他加工,如生成代理对象(proxy);

6.getSingleton(beanName, false) 依次在一二级缓存中查找,检查是否有因循环依赖导致提前生成的对象,有的话与初始化后的对象是否一致;

二、Spring 用三级缓存解决循环依赖的

我们通过例子学习,单例的setter注入,这种注入方式应该是spring用的最多的,代码如下:

@Service
public class TestService1 {

    @Autowired
    private TestService2 testService2;

    public void test1() {
    }
}

@Service
public class TestService2 {

    @Autowired
    private TestService1 testService1;

    public void test2() {
    }

这是一个经典的循环依赖,但是它能正常运行,得益于spring的内部机制使用三级缓存,让我们根本无法感知它有问题,因为spring默默帮我们解决了。

用一张图告诉你,spring是如何解决循环依赖的:

请添加图片描述

三、为什么 Sping 不选择二级缓存方式,而是要额外加一层缓存?

如果要使用二级缓存解决循环依赖,意味着 Bean 在构造完后就创建代理对象,这样违背了 Spring 设计原则。 Spring 结合 AOP 跟 Bean 的生命周期,是在 Bean 创建完全之后通过 AnnotationAwareAspectJAutoProxyCreator 这个后置处理器来完成的,在这个后置处理的 postProcessAfterInitialization 方法中对初始化后的 Bean 完成 AOP 代理。

如果出现了循环依赖,那没有办法,只有给 Bean 先创建代理,但是没有出现循环依赖的情况下,设计之初就是让 Bean 在生命周期的最后一步完成代理而不是在实例化后就立马完成代理。

这实际上涉及到 AOP,如果创建的 Bean 是有代理的,那么注入的就应该是代理 Bean,而不是原始的 Bean。但是 Spring 一开始并不知道 Bean 是否会有循环依赖,通常情况下(没有循环依赖的情况下),Spring 都会在完成填充属性,并且执行完初始化方法之后再为其创建代理。但是,如果出现了循环依赖的话,Spring 就不得不为其提前创建代理对象,否则注入的就是一个原始对象,而不是代理对象。因此,这里就涉及到应该在哪里提前创建代理对象。

是否会有循环依赖,通常情况下(没有循环依赖的情况下),Spring 都会在完成填充属性,并且执行完初始化方法之后再为其创建代理。但是,如果出现了循环依赖的话,Spring 就不得不为其提前创建代理对象,否则注入的就是一个原始对象,而不是代理对象。因此,这里就涉及到应该在哪里提前创建代理对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值