乐趣区

关于java:求求你再别问Spring循环依赖了44张图源码解析

一、循环依赖产生的起因

简略形容一下啥叫循环依赖, 简略来说就是俄罗斯套娃,你中有我,我中有你。

圆和三角别离示意两个 bean 对象,圆外面援用了三角,三角外面援用了圆,这个就是循环依赖所产生的后果了。

在理论业务场景中其实也很多见,比方两个服务,一个是用户服务,一个是订单服务。

1. 用户会通过本人的账号来查问本人的订单信息,这时候一种计划就须要在用户服务外面调用订单服务查找特定用户的订单列表。

2. 商家会通过所有的订单信息依照用户进行分组,这时候就须要在订单服务外面调用用户服务来查找一批订单对应的用户的详细信息。

因为业务边界有时候很难划分分明,就会随同这种循环依赖的产生。然而同学们可能会说,可是我平时 @Autowired 的时候是那么无拘无束,自由自在,从不报错,那是因为 spring 曾经帮咱们解决了这个问题了。

二、代码筹备

代码目录构造如下

1.InstanceA

public class InstanceA {
    private InstanceB b;

    public InstanceB getB() {return b;}

    public void setB(InstanceB b) {this.b = b;}
}
复制代码

2.InstanceB

public class InstanceB {

    private InstanceA a;

    public InstanceA getA() {return a;}

    public void setA(InstanceA a) {this.a = a;}
}
复制代码

3.aa.xml 配置文件

<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"
  xmlns:util="http://www.springframework.org/schema/util"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
    http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.3.xsd">

  <!-- 1. 应用 property 初始化 Bean 属性 -->
  <bean id="a" class="com.lyf.spring.config.InstanceA">
      <property name="b" ref="b"></property>
  </bean>


  <bean id="b" class="com.lyf.spring.config.InstanceB">
      <property name="a" ref="a"></property>
  </bean>
</beans>
复制代码

4.main 函数

public static void main(String[] args) {ApplicationContext xc = new ClassPathXmlApplicationContext("aa.xml");
        InstanceA instanceA = xc.getBean(InstanceA.class);
    }
复制代码

三、图解 + 源码

我始终在想用文字如何形容比拟好,思考下来还是把关键点一步一步地和大家一起来看下,最初把流程图也给大家贴出来,如果形容不分明的能够配合流程图来进行调试。

首先须要大家分明的是三级缓存其实指的就是 3 个 map,这三个名字请大家务必记牢

上面咱们开始进入调试

1. 找到入口办法 refresh()

找到 refresh() 办法,这个是咱们 bean 实例化的入口,也是咱们本次调试循环依赖的入口

2. 进入 refresh()找到执行实例化动作的 finishBeanFactoryInitialization(beanFactory)

3. 进入 finishBeanFactoryInitialization(beanFactory)找到 beanFactory.preInstantiateSingletons()

4.preInstantiateSingletons()办法外部找到 getBean(beanName)

1. 通过循环来别离执行 InstanceA 和 InstanceB 的实例化和初始化

2. 通过一系列判断,进入 else 逻辑找到 getBean(beanName)

// 判断是否是抽象类 / 单例 / 懒加载
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
    // 判断是否继承 FactoryBean
    if (isFactoryBean(beanName))
复制代码

5. 通过 getBean(beanName)找到 doGetBean 进入其实现类重点看下 getSingleton(beanName)

进入到 getSingleton(beanName)外面,这个办法须要大家记住,咱们实例化好的和初始化好的对象都会通过这个办法来从各级缓存中去尝试读取。

 /** Names of beans that are currently in creation */
    private final Set<String> singletonsCurrentlyInCreation =
            Collections.newSetFromMap(new ConcurrentHashMap<>(16));
复制代码

这个汇合须要记一下,用于记录以后处于创立阶段的 bean 的 beanname,因为以后咱们在实例化 a 的时候还没有进入到创立阶段,仅仅只是去缓存中尝试获取 a,如果存在就间接拿到了,当初是不存在,所以返回 null

6. 因为一级缓存中不存在 a,并且 a 也不处于 create 阶段,所以此时须要去触发 a 的实例化

7. 进去 getObject()进去匿名外部类中,createBean(beanName, mbd, args)

8. 找到 doCreateBean(beanName, mbdToUse, args)办法

9. 进入 doCreateBean(beanName, mbdToUse, args)

找到上面这个办法,这个办法的作用就是把 a 及其匿名外部类放入三级缓存

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
复制代码

1. 判断传入的 ObjectFactory 是否为空

2. 给一级缓存加锁

3. 判断一级缓存中是否存在 a,显然是不存在的

4. 把 a 及其 ObjectFactory 放入 singletonFactories 也就是三级缓存中

到这一步咱们的三级缓存中终于有货色进来了当初的状态如下图

bean a 目前是这种状态,所以接下来须要把 a 外面的元素填充好

10 这里咱们曾经把 a 放入了三级缓存,开始填充 bean

找到要害行

这里咱们略微跳一跳,因为两头波及的校验操作比拟多。

进入populateBean

找到最初一行

applyPropertyValues(beanName, mbd, bw, pvs);
复制代码

毫不犹豫的进入,啥也不论,冲就完事,跑到这个 for 循环

11. 去缓存中去找 b 是否存在

找到这行要害代码

Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
复制代码

始终跑咱们发现最初进到了咱们最后的 doGetBean 外面

是不是似曾相识很相熟的感觉,找到老乡了。

参考:《2020 最新 Java 根底精讲视频教程和学习路线!》
链接:https://juejin.cn/post/694269…

退出移动版