方案导入
循环依赖是什么
构造出两个对象 A 和 B,A 中有成员 B,B 中有成员 A,换成代码就是这样子。
@Componentpublic class A { @Autowired private B b;} @Componentpublic class B { @Autowired private A a;}
复制代码
循环依赖会带来什么问题
如果创建 A 对象,那必须注入 B 对象,注入 B 对象又需要创建 A 对象,如此反复,直到 OOM
如何解决循环依赖问题
我们想象有这样一幅有向图,我们从 A 开始,到 D 结束,当执行到 D 的时候,D 还是会继续执行,因为 D 不知道 A 是否被经过。
为了解决这一问题,我们只需要将经过的路径点染色就好了。
D 发现 A 已经是红色,知道已经被途径过,主动结束循环。
我们很容易就能发现,循环依赖问题和图路径中是否有环问题是一样的,就能保证 Bean(实例)不被重复创建。
联系 Spring
都说 Spring 三级缓存解决了循环依赖问题,那我们就使用了一级缓存就解决了缓存依赖问题,spring 的开发团队怎么会傻到用三级缓存解决问题,当然这句话可能还有一个歧义,第三层缓存区解决了缓存依赖问题,这同样也是错的,且听下文分析。
三级缓存是什么
// 存储单例的Bean对象private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);// 存储早期曝光的单例Bean对象,只是一个Bean的引用,未初始化private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);// 存储单例Bean对象的工厂private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);
复制代码
三级缓存如何工作 - 源码部分
我会对关键代码打注释,你这么聪明肯定一下就看懂了
当我们获取想要注入的单例时,有以下代码,删去了 try - catch 的异常处理和解决并发问题的代码块,便于清晰的阅读
public Object getSingleton(String beanName) { return getSingleton(beanName, true);} protected Object getSingleton(String beanName, boolean allowEarlyReference) { //从一级缓存中获取Bean Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { //从二级缓存中获取Bean singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { //从三级缓存(工厂)中获取Bean ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName); if (singletonFactory != null) { singletonObject = singletonFactory.getObject(); //从工厂中获取到 singletonObject 时,从工厂缓存中删去工厂,工厂创建的对象加入二级缓存 if (this.singletonFactories.remove(beanName) != null) { this.earlySingletonObjects.put(beanName, singletonObject); } } } } return singletonObject;}
复制代码
可见 getSingleton 的路径是 一级缓存 → 二级缓存 → 三级缓存,同时当从三级缓存中获取到早期对象时,直接放入二级缓存,删除三级缓存(后续的多次引用也是二级缓存),可见二级缓存+短暂的三级缓存相当于标记 bean 为已实例化,所以依赖三级缓存解决循环依赖显然是错的
那三级缓存(工厂)到底存储着什么,不是二级缓存就能解决问题了吗?我们在 doCreateBean 中可以看到以下代码。
// 当前 Bean(例如 A)在实例化后、依赖注入和初始化完成前,是否需要将其作为“早引用”暴露给其他 Bean boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); // 允许被早引用(早期曝光) if (earlySingletonExposure) { // 添加到单例工厂(三级缓存) addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // 被三级缓存添加后再进行初始化 Object exposedObject = bean; populateBean(beanName, mbd, instanceWrapper); exposedObject = initializeBean(beanName, exposedObject, mbd);
复制代码
证明了三级缓存以及二级缓存中的对象是引用对象,未被真正初始化,等于是一个懒加载,同样也不会造成循环依赖,因为其内部的对象没有只引用了实例化的对象,未被初始化。
getEarlyBeanReference 应该有关于 Factory 中的一些信息
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; // 是否被代理(AOP,字节码增强) if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) { exposedObject = bp.getEarlyBeanReference(exposedObject, beanName); } } return exposedObject; }
复制代码
倘若没有经过字节码增强代码可以缩略成两行
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { Object exposedObject = bean; return exposedObject; }
复制代码
诶卧槽,这不是啥也没干!
三级缓存只参与了 AOP 对象的返回,解决 bean 的 AOP 代理问题
下图展示了 bean 的初始化过程
那我们现在可以得出以下结论了:
第一级缓存用来简单返回缓存后的 bean 对象。
第二级缓存就可以解决循环依赖问题。
二三级缓存只保留了实例化的 bean,未初始化,不会导致循环依赖。
第三级缓存用来解决 Bean 的代理类的实例化问题。
文章转载自:crhl-yy
原文链接:https://www.cnblogs.com/many-bucket/p/18971691
体验地址:http://www.jnpfsoft.com/?from=001YH
评论