大家好,我是鸭鸭!
鸭鸭发现,最近的大学生,不玩金铲铲之战了,而是打开了一个神秘的 app ——小猿口算。
app 里有一个 PK 功能,用户可以通过 PK 练习加强数学基础运算能力。一般来说,这个功能的受众是小学生,题目也都比较简单。诸如比较数字大小,或者加减乘除的简单口算。
但闲着无聊的大学生们愣是把一个小学生学习软件玩成了电子竞技游戏。也因此被网友称为大学生炸鱼。
打开 app 的评论区,有控诉严查外挂的,也有家长抱怨孩子在 PK 输到哭了。
大学生不仅占据了小猿口算的 PK 功能,还通过改名,拉了一波嘲讽:
是时候严查大学生的精神状态了!
随着小猿口算的热度飙升,大学生也不再满足于亲自做题 PK ,而是选择了借助各种脚本,比拼答题速度。
妈妈!这下是真的有外挂了!
而且大家明显不满足于现有的脚本,开始连夜改算法。
小猿口算这波战火升级成了各大高校之间的算法对决。排行榜上的数据也是越发离谱。
因此网上又开始流传猿辅导急聘高级反爬算法工程师。
今天官方发布了功能升级公告,网上流传一张新版的巅峰对决题目,这下,大学生们还能秒答对吗?
鸭鸭觉得,想刷题不如来面试鸭,9000+高频面试题,大厂面试官精心原创题解,免费刷题复习路线,到时候直接吊打面试官,这才是爽文情节!
今天的题目鸭鸭已经准备好了,大家一起来看看吧。
为什么 Spring 循环依赖需要三级缓存,二级不够吗?
回答重点
Spring 之所以需要三级缓存而不是简单的二级缓存,主要原因在于AOP代理和Bean的早期引用问题。
二级缓存虽然可以解决循环依赖的问题,但在涉及到动态代理(AOP) 时,直接使用二级缓存不做任何处理会导致我们拿到的 Bean 是未代理的原始对象。如果二级缓存内存放的都是代理对象,则违反了 Bean 的生命周期。
扩展知识
进一步理解分析为什么需要三级缓存
很明显,如果仅仅只是为了破解循环依赖,二级缓存够了,压根就不必要三级。
你思考一下,在实例化 Bean A 之后,我在二级 map 里面塞入这个 A,然后继续属性注入,发现 A 依赖 B 所以要创建 Bean B,这时候 B 就能从二级 map 得到 A ,完成 B 的建立之后, A 自然而然能完成。
所以为什么要搞个三级缓存,且里面存的是创建 Bean 的工厂呢?
我们来看下调用工厂的 getObject 到底会做什么,实际会调用下面这个方法:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}Copy to clipboardErrorCopied
重点就在中间的判断,如果 false,返回就是参数传进来的 bean,没任何变化。
如果是 true 说明有 InstantiationAwareBeanPostProcessors ,且循环的 smartInstantiationAware 类型,如有这个 BeanPostProcessor 说明 Bean 需要被 aop 代理。
我们都知道如果有代理的话,那么我们想要直接拿到的是代理对象,也就是说如果 A 需要被代理,那么 B 依赖的 A 是已经被代理的 A,所以我们不能返回 A 给 B,而是返回代理的 A 给 B。
这个工厂的作用就是判断这个对象是否需要代理,如果否则直接返回,如果是则返回代理对象。
看到这明白的小伙伴肯定会问,那跟三级缓存有什么关系,我可以在要放到二级缓存的时候判断这个 Bean 是否需要代理,如果要直接放代理的对象不就完事儿了。
是的,这个思路看起来没任何问题,问题就出在时机,这跟 Bean 的生命周期有关系。
正常代理对象的生成是基于后置处理器,是在被代理的对象初始化后期调用生成的,所以如果你提早代理了其实是违背了 Bean 定义的生命周期。
所以 Spring 先在一个三级缓存放置一个工厂,如果产生循环依赖,那么就调用这个工厂提早得到代理对象,如果没产生依赖,这个工厂根本不会被调用,所以 Bean 的生命周期就是对的。
至此,我想你应该明白为什么会有三级缓存了。
也明白,其实破坏循环依赖,其实只有二级缓存就够了,但是碍于生命周期的问题,提前暴露工厂延迟代理对象的生成。
对了,不用担心三级缓存因为没有循环依赖,数据堆积的问题,最终单例 Bean 创建完毕都会加入一级缓存,此时会清理下面的二、三级缓存。
最后
再来推荐下我们的面试刷题神器:面试鸭!网站和小程序双端可用!