三级缓存解决循环依赖

三级缓存解决循环依赖

概括

在项目中使用spring,对象的创建是交给了spring去管理的,那么spring是如何创建对象的呢,这里涉及了bean的生命周期,这里不细说,下篇再讨论。可以提前透露底层用的是反射。

  1. 首先spring需要获取这个beanclass文件 (扫描包)
  2. 根据反射获取这个class的空构造函数(所以如果存在依赖循环 那么必须存在空的构造函数)
  3. 设置bean对象的属性
    • 如果属性是一个对象的话, 就去单例池中获取,如果单例池中不存在,那么就再 创 建 \color{green}{创建} 这个属性对象
    • 创建完毕之后,放入单例池中。然后再赋值给对象的属性,创建完毕之后在放入单例池中

那 么 如 果 正 在 创 建 的 属 性 对 象 也 依 赖 于 正 在 创 建 的 对 象 呢 ? 这 里 就 出 现 了 依 赖 循 环 。 既 然 对 象 的 创 建 是 由 s p r i n g 来 管 理 的 , 那 就 看 一 下 s p r i n g 是 如 何 解 决 这 个 依 赖 循 环 的 \color{red}{那么如果正在创建的属性对象也依赖于正在创建的对象呢?这里就出现了依赖循环。既然对象的创建是由spring来管理的,那就看一下spring是如何解决这个依赖循环的} springspring

正题

存在2个对象,AServiceBService。以下简称A对象B对象

@Service
public class AService {
    @Autowired
    private BService bService;
}
@Service
public class BService {
    @Autowired
    private AService aService;
}

spring扫描到@Service注解的时候,就会创建这个A对象。大致过程

  1. 拿到A对象class文件。通过反射根据A对象的空构造函数获取A对象 原 始 对 象 \color{red}{原始对象}
  2. 属性赋值–A对象有一个属性为B对象,那么就给B对象赋值
  3. 给其他的属性赋值
  4. 其他事情(比如AOP)
  5. 放入单例池中 (单例池就是一级缓存)

可以发现 循 环 依 赖 \color{red}{循环依赖} 发生在第二步

  • 当给B对象赋值的时候,首先会去单例池中根据B对象的名称,也就是beanName也就是一级缓存中获取
  • 如果一级缓存不存在的话 那么就会创建B对象,也就是创建B对象,会把对象创建的过程在走一遍
  • 那么同样的B对象依赖A对象,由于A对象是刚刚正在创建的对象,且没有创建完毕,卡住了在B对象赋值的过程,所以单例池中也不存在。此时又会创建一个A对象,这样就会产生了一个 死 循 环 \color{red}{死循环}

显然spring是不会再创建对象的时候出现这种情况的,那么spring是如何解决这个问题的呢?
这里就引用了一个三级缓存三级缓存也是一个HashMapkey时创建bean的名称,而value就是根据空的构造函数创建的原始对象。没有任何属性

那么上述步骤就可以改为

  1. 拿到A对象class文件,通过反射根据A对象的构造函数获取A对象的原始对象,并且将A对象的原始对象放入到三级缓存中,map.put("aService",AService)
  2. 属性赋值-获取A对象有一个B对象,给B对象赋值
    2.1. 从单例池中获取B对象,首先回去一级缓存中根据beanName查找,如果不存在—创建B对象
    2.2:拿到B对象class文件 .通过反射根据B对象的空构造函数获取B对象的原始对,并且将B对象的原始对象放入三级缓存*,map.put("bService",AService)
    2.3:给B对象aService属性赋值,从一级缓存中查找,如果找不到 去三级缓存中查找,将三级缓存中的原始对象赋值给B对象
    2.4:给其他属性赋值
    2.5:其他事情(AOP)
    2.6:放入单例池
  3. 给其他属性赋值
  4. 其他事情(AOP)
  5. 放入单例池

这 样 就 解 决 了 循 环 依 赖 吗 ? B 对 象 中 的 A 对 象 一 个 原 始 对 象 , 那 么 会 是 一 个 完 整 的 A 对 象 吗 ? 这 里 明 明 只 有 一 个 一 级 缓 存 和 三 级 缓 存 , 那 么 二 级 缓 存 去 哪 里 了 ? 有 必 要 需 要 二 级 缓 存 吗 \color{red}{这样就解决了循环依赖吗 ? B对象中的A对象一个原始对象,那么会是一个完整的A对象吗?这里明明只有一个一级缓存和三级缓存,那么二级缓存去哪里了?有必要需要二级缓存吗} ?BAA?

答案必须需要存在一个二级缓存。

因为B对象中的aService只是一个 地 址 引 用 \color{green}{地址引用} ,当B对象创建完毕后,再次回到A对象的创建,此时A对象也是同一个引用对象,在对A对象进行一系列后续操作。最后成一个完成的A对象,尤其是进行AOP过后,返回的并不是A对象的引用地址,而是A对象代理对象的引用地址。单例池中存放的也是代理对象。而此时B对象引用的还是A对象原始对象的引用地址,这就是问题所在。

那 么 S p r i n g 又 是 如 何 解 决 这 个 问 题 的 呢 ? \color{red}{那么Spring又是如何解决这个问题的呢?} Spring

如果我们能够提前AOP,将AOP过后的A对象赋值给B对象,那么就解决了问题

所以将AOP后的对象放入到一个缓存中,也就是二级缓存中,但又不是所有的AOP都是提前的。只有知道当前创建的bean是产生了循环依赖,那么如何得知当前bean产生了循环依赖。我们可以这样做。

  • 创建A对象的时候,设置一个标记位。比如一个Set
  • 创建A对象的时候 将A对象添加进入Set集合中,当给B对象的属性A对象赋值的时候,发现了需要对象A,那么去缓存中拿对象的前面判断此时A对象是否正在被创建,也是就Set集合中是否存在对象A,如果存在 那么就提前AOP,如果不存在 那么就正常流程。

同样的上述步骤可以改成

  1. 拿到A对象class文件,通过反射根据A对象的空构造函数获取A对象的原始对象并且将A对象的原始对象放入到三级缓存*中 map.put("aService",AService)
  2. 属性赋值: 获取A对象有一个B对象,给B对象赋值
    • 单例池中获取B对象
    • 一级缓存中不存在,创建B对象,拿到B对象class文件
    • 通过反射根据B对象的空构造函数获取B对象的原始对象并且将B对象的原始对象放入到三级缓存中,map.put("bService",BService)
    • B对象aService属性赋值,判断是否提前AOP
    • 需要提前AOP表示了循环依赖),将三级缓存中原始对象A取出,进行提前AOP,将AOP过后的A对象代理对象放到二级缓存中,并且 删 除 \color{red}{删除} 三级缓存中的A对象,删除的目的是为了防止重复AOP,此时B对象将二级缓存中的A对象的代理对象赋值给自己的A对象属性
    • 给其他属性赋值
    • 其他事情
    • 放入单例池(将完整的B对象放入单例池中)
  3. 给其他属性赋值
  4. 其他事情(此时不需要进行AOP)
  5. 放入单例池中并且删除二级缓存中的A对象的代理对象

下面从源码角度分析一边

   //一级缓存
  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);

    @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
            Map var4 = this.singletonObjects;
            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;
    }

获取Bean的时候首先去一级缓存拿
如果一级缓存中不存在 那么就去二级缓存中获取,
如果二级缓存中也不存在的话 就去三级缓存中拿,
三级缓存中一定存在。三级缓存中再进行一些列操作之后,将这个对象放入二级缓存中,再将三级缓存中移除

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值