玫瑰不慌张 發表於 2025-7-7 21:50:00

三级缓存解决了循环依赖问题?别被骗了,一级缓存就够了!

<h2 id="方案导入">方案导入</h2>
<h3 id="循环依赖是什么">循环依赖是什么</h3>
<p>构造出两个对象A和B,A中有成员B,B中有成员A,换成代码就是这样子。</p>
<pre><code class="language-java">@Component
public class A {
    @Autowired
    private B b;
}

@Component
public class B {
    @Autowired
    private A a;
}
</code></pre>
<h3 id="循环依赖会带来什么问题">循环依赖会带来什么问题</h3>
<p>如果创建A对象,那必须注入B对象,注入B对象又需要创建A对象,如此反复,直到OOM</p>
<p><img src="https://img2024.cnblogs.com/blog/3642195/202507/3642195-20250707214028147-389894272.png"></p>
<h3 id="如何解决循环依赖问题">如何解决循环依赖问题</h3>
<p>我们想象有这样一幅有向图,我们从A开始,到D结束,当执行到D的时候,D还是会继续执行,因为D不知道A是否被经过。</p>
<p><img src="https://img2024.cnblogs.com/blog/3642195/202507/3642195-20250707214037311-1628211088.png"></p>
<p>为了解决这一问题,我们只需要将经过的路径点<code>染色</code>就好了。</p>
<p><img src="https://img2024.cnblogs.com/blog/3642195/202507/3642195-20250707214044854-1473465357.png"></p>
<p>D发现A已经是红色,知道已经被途径过,主动结束循环。</p>
<p>我们很容易就能发现,循环依赖问题和图路径中是否有环问题是一样的,就能保证Bean(实例)不被重复创建。</p>
<p><img src="https://img2024.cnblogs.com/blog/3642195/202507/3642195-20250707214053641-253448483.png"></p>
<h2 id="联系spring">联系Spring</h2>
<p>都说Spring三级缓存解决了循环依赖问题,那我们就使用了一级缓存就解决了缓存依赖问题,spring的开发团队怎么会傻到用三级缓存解决问题,当然这句话可能还有一个歧义,第三层缓存区解决了缓存依赖问题,这同样也是错的,且听下文分析。</p>
<h3 id="三级缓存是什么">三级缓存是什么</h3>
<pre><code class="language-java">//存储单例的Bean对象
private final Map&lt;String, Object&gt; singletonObjects = new ConcurrentHashMap&lt;&gt;(256);
//存储早期曝光的单例Bean对象,只是一个Bean的引用,未初始化
private final Map&lt;String, Object&gt; earlySingletonObjects = new ConcurrentHashMap&lt;&gt;(16);
//存储单例Bean对象的工厂
private final Map&lt;String, ObjectFactory&lt;?&gt;&gt; singletonFactories = new ConcurrentHashMap&lt;&gt;(16);
</code></pre>
<h3 id="三级缓存如何工作---源码部分">三级缓存如何工作 - 源码部分</h3>
<blockquote>
<p>我会对关键代码打注释,你这么聪明肯定一下就看懂了</p>
</blockquote>
<p>当我们获取想要注入的单例时,有以下代码,删去了 try - catch的异常处理和解决并发问题的代码块,便于清晰的阅读</p>
<pre><code class="language-java">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 &amp;&amp; isSingletonCurrentlyInCreation(beanName)) {
                //从二级缓存中获取Bean
      singletonObject = this.earlySingletonObjects.get(beanName);
      if (singletonObject == null &amp;&amp; allowEarlyReference) {
                  //从三级缓存(工厂)中获取Bean
            ObjectFactory&lt;?&gt; 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;
}
</code></pre>
<p>可见 <code>getSingleton</code> 的路径是 一级缓存 → 二级缓存→ 三级缓存,同时当从三级缓存中获取到早期对象时,直接放入二级缓存,删除三级缓存(后续的多次引用也是二级缓存),可见二级缓存+短暂的三级缓存相当于标记bean为已实例化,所以依赖三级缓存解决循环依赖显然是错的</p>
<p>那三级缓存(工厂)到底存储着什么,不是二级缓存就能解决问题了吗?我们在 <code>doCreateBean</code> 中可以看到以下代码。</p>
<pre><code class="language-java">                // 当前 Bean(例如 A)在实例化后、依赖注入和初始化完成前,是否需要将其作为“早引用”暴露给其他 Bean
                boolean earlySingletonExposure = (mbd.isSingleton() &amp;&amp; this.allowCircularReferences &amp;&amp;
                                isSingletonCurrentlyInCreation(beanName));
                // 允许被早引用(早期曝光)
                if (earlySingletonExposure) {
                        // 添加到单例工厂(三级缓存)
                        addSingletonFactory(beanName, () -&gt; getEarlyBeanReference(beanName, mbd, bean));
                }
               
                // 被三级缓存添加后再进行初始化
                Object exposedObject = bean;
                populateBean(beanName, mbd, instanceWrapper);
                exposedObject = initializeBean(beanName, exposedObject, mbd);

</code></pre>
<p>证明了三级缓存以及二级缓存中的对象是引用对象,未被真正初始化,等于是一个懒加载,同样也不会造成循环依赖,因为其内部的对象没有只引用了实例化的对象,未被初始化。</p>
<p><code>getEarlyBeanReference</code> 应该有关于Factory中的一些信息</p>
<pre><code class="language-java">        protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
                Object exposedObject = bean;
                // 是否被代理(AOP,字节码增强)
                if (!mbd.isSynthetic() &amp;&amp; hasInstantiationAwareBeanPostProcessors()) {
                        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
                                exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
                        }
                }
                return exposedObject;
        }
</code></pre>
<p>倘若没有经过字节码增强代码可以缩略成两行</p>
<pre><code class="language-java">        protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
                Object exposedObject = bean;
                return exposedObject;
        }
</code></pre>
<p>诶卧槽,这不是啥也没干!</p>
<p>三级缓存只参与了AOP对象的返回,解决bean的AOP代理问题</p>
<p>下图展示了bean的初始化过程</p>
<p><img src="https://img2024.cnblogs.com/blog/3642195/202507/3642195-20250707214105375-1531597703.png"></p>
<p>那我们现在可以得出以下结论了:</p>
<ol>
<li>第一级缓存用来简单返回缓存后的bean对象。</li>
<li>第二级缓存就可以解决循环依赖问题。</li>
<li>二三级缓存只保留了实例化的bean,未初始化,不会导致循环依赖。</li>
<li>第三级缓存用来解决Bean的代理类的实例化问题。</li>
</ol>
<hr>
<p>如果这篇文章对你有所帮助,请给我点个赞。你的肯定会让我非常开心。🥰🥰🥰</p>
<p>求点赞 orz OTZ !!</p><br><br>
来源:https://www.cnblogs.com/many-bucket/p/18971691
頁: [1]
查看完整版本: 三级缓存解决了循环依赖问题?别被骗了,一级缓存就够了!