D

Dut Blog

这里是 @Dut段某人 的个人博客 | 世界上并没有完美的程序,但我们并不因此而沮丧,因为写程序本来就是一个不断追求完美的过程。

从架构角度结合分布式缓存和本地缓存

<blockquote> <p>在工作中,有时候会碰到这样一种情况,一个产品本身就是以一个单体应用去开发设计的,但架不住规模的变化,需要调整为多节点集群部署。</p> <p>那碰到这种既需要单节点部署,又需要多节点分布式部署的场景,缓存业务如何进行自适应调整</p> </blockquote> <h1 id="需求分析">需求分析</h1> <p>面对这种场景,糙一点的方式无非是搞两套实现,一套是单节点部署,采用本地缓存,另一套采用分布式缓存,适用于多节点集群部署。</p> <p>站在设计的角度上这很合理,但不够优雅。。。</p> <p>愿景:把本地缓存和分布式缓存结合起来,提供统一的 API 去处理,在具体的场景中自适应具体的实现。</p> <p>优点:</p> <ul> <li>统一实现,不必考虑部署场景</li> <li>底层实现更方便替换</li> <li>提供额外特性 <ul> <li>超时过期</li> <li>缓存空值</li> <li>初始化清理</li> <li>…</li> </ul> </li> <li>…根据设计自行扩展</li> </ul> <h1 id="设计">设计</h1> <ol> <li> <p>定义缓存操作接口</p> <p>两个接口,分别应对于Map接口和常量接口</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">AdaptingCache</span><span class="o">&lt;</span><span class="no">K</span><span class="o">,</span> <span class="no">V</span><span class="o">&gt;</span> <span class="o">{</span> <span class="cm">/** * 获取缓存名称 * * @return 缓存名称 */</span> <span class="nc">String</span> <span class="nf">getName</span><span class="o">();</span> <span class="cm">/** * 获取底层具体实现 * * @return 底层具体实现,Redis为RedisCache */</span> <span class="nc">Cache</span> <span class="nf">getNativeCache</span><span class="o">();</span> <span class="cm">/** * 获取缓存值 * * @param key 缓存key * @return 缓存值 */</span> <span class="no">V</span> <span class="nf">get</span><span class="o">(</span><span class="no">K</span> <span class="n">key</span><span class="o">);</span> <span class="cm">/** * 获取缓存值 * * @param key 缓存key * @param valueLoader 不存在则加载,并保存到缓存中 * @return 缓存值 */</span> <span class="no">V</span> <span class="nf">get</span><span class="o">(</span><span class="no">K</span> <span class="n">key</span><span class="o">,</span> <span class="nc">Callable</span><span class="o">&lt;</span><span class="no">V</span><span class="o">&gt;</span> <span class="n">valueLoader</span><span class="o">);</span> <span class="cm">/** * 添加缓存 * * @param key 缓存key * @param value 缓存值 */</span> <span class="kt">void</span> <span class="nf">put</span><span class="o">(</span><span class="no">K</span> <span class="n">key</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="no">V</span> <span class="n">value</span><span class="o">);</span> <span class="cm">/** * 添加缓存并返回 * * @param key 缓存key * @param value 缓存值 * @return 缓存值 */</span> <span class="k">default</span> <span class="no">V</span> <span class="nf">putIfAbsent</span><span class="o">(</span><span class="no">K</span> <span class="n">key</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="no">V</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span> <span class="no">V</span> <span class="n">existingValue</span> <span class="o">=</span> <span class="n">get</span><span class="o">(</span><span class="n">key</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">existingValue</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">put</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">);</span> <span class="o">}</span> <span class="k">return</span> <span class="n">existingValue</span><span class="o">;</span> <span class="o">}</span> <span class="cm">/** * 移除缓存 * * @param key 缓存key */</span> <span class="kt">void</span> <span class="nf">evict</span><span class="o">(</span><span class="no">K</span> <span class="n">key</span><span class="o">);</span> <span class="cm">/** * 清理全部缓存 */</span> <span class="kt">void</span> <span class="nf">clear</span><span class="o">();</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div> </div> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">AdaptingValueCache</span><span class="o">&lt;</span><span class="no">V</span><span class="o">&gt;</span> <span class="o">{</span> <span class="cm">/** * 获取缓存名称 * * @return 缓存名称 */</span> <span class="nc">String</span> <span class="nf">getName</span><span class="o">();</span> <span class="cm">/** * 获取缓存值 * * @return 缓存值 */</span> <span class="no">V</span> <span class="nf">get</span><span class="o">();</span> <span class="cm">/** * 获取缓存值 * * @param valueLoader 若不存在则添加至缓存中 * @return 缓存值 */</span> <span class="no">V</span> <span class="nf">get</span><span class="o">(</span><span class="nc">Callable</span><span class="o">&lt;</span><span class="no">V</span><span class="o">&gt;</span> <span class="n">valueLoader</span><span class="o">);</span> <span class="cm">/** * 添加缓存值 * * @param value */</span> <span class="kt">void</span> <span class="nf">put</span><span class="o">(</span><span class="nd">@Nullable</span> <span class="no">V</span> <span class="n">value</span><span class="o">);</span> <span class="cm">/** * 添加缓存值,并返回 * * @param value 缓存值 * @return 缓存值 */</span> <span class="k">default</span> <span class="no">V</span> <span class="nf">putIfAbsent</span><span class="o">(</span><span class="nd">@Nullable</span> <span class="no">V</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span> <span class="no">V</span> <span class="n">existingValue</span> <span class="o">=</span> <span class="n">get</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">existingValue</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">put</span><span class="o">(</span><span class="n">value</span><span class="o">);</span> <span class="o">}</span> <span class="k">return</span> <span class="n">existingValue</span><span class="o">;</span> <span class="o">}</span> <span class="cm">/** * 清理缓存 */</span> <span class="kt">void</span> <span class="nf">clear</span><span class="o">();</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div> </div> </li> <li> <p>抽象桥接 Spring cache</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">AbstractAdaptingCache</span><span class="o">&lt;</span><span class="no">K</span><span class="o">,</span> <span class="no">V</span><span class="o">&gt;</span> <span class="kd">implements</span> <span class="nc">AdaptingCache</span><span class="o">&lt;</span><span class="no">K</span><span class="o">,</span> <span class="no">V</span><span class="o">&gt;</span> <span class="o">{</span> <span class="kd">protected</span> <span class="nc">Cache</span> <span class="n">adapter</span><span class="o">;</span> <span class="kd">public</span> <span class="nf">AbstractAdaptingCache</span><span class="o">(</span><span class="nc">Cache</span> <span class="n">adapter</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">adapter</span> <span class="o">=</span> <span class="n">adapter</span><span class="o">;</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="nc">String</span> <span class="nf">getName</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">adapter</span><span class="o">.</span><span class="na">getName</span><span class="o">();</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="nc">Cache</span> <span class="nf">getNativeCache</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">adapter</span><span class="o">;</span> <span class="o">}</span> <span class="nd">@SuppressWarnings</span><span class="o">(</span><span class="s">"unchecked"</span><span class="o">)</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="no">V</span> <span class="nf">get</span><span class="o">(</span><span class="no">K</span> <span class="n">key</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Cache</span><span class="o">.</span><span class="na">ValueWrapper</span> <span class="n">valueWrapper</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">adapter</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">key</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">valueWrapper</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="o">(</span><span class="no">V</span><span class="o">)</span><span class="n">valueWrapper</span><span class="o">.</span><span class="na">get</span><span class="o">();</span> <span class="o">}</span> <span class="k">return</span> <span class="kc">null</span><span class="o">;</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="no">V</span> <span class="nf">get</span><span class="o">(</span><span class="no">K</span> <span class="n">key</span><span class="o">,</span> <span class="nc">Callable</span><span class="o">&lt;</span><span class="no">V</span><span class="o">&gt;</span> <span class="n">valueLoader</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">adapter</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">valueLoader</span><span class="o">);</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">put</span><span class="o">(</span><span class="no">K</span> <span class="n">key</span><span class="o">,</span> <span class="no">V</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">adapter</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">);</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">evict</span><span class="o">(</span><span class="no">K</span> <span class="n">key</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">adapter</span><span class="o">.</span><span class="na">evict</span><span class="o">(</span><span class="n">key</span><span class="o">);</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">clear</span><span class="o">()</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">adapter</span><span class="o">.</span><span class="na">clear</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div> </div> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">AbstractAdaptingValueCache</span><span class="o">&lt;</span><span class="no">V</span><span class="o">&gt;</span> <span class="kd">implements</span> <span class="nc">AdaptingValueCache</span><span class="o">&lt;</span><span class="no">V</span><span class="o">&gt;</span> <span class="o">{</span> <span class="kd">protected</span> <span class="nc">Cache</span> <span class="n">adapter</span><span class="o">;</span> <span class="kd">public</span> <span class="nf">AbstractAdaptingValueCache</span><span class="o">(</span><span class="nc">Cache</span> <span class="n">adapter</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">adapter</span> <span class="o">=</span> <span class="n">adapter</span><span class="o">;</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="nc">String</span> <span class="nf">getName</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">adapter</span><span class="o">.</span><span class="na">getName</span><span class="o">();</span> <span class="o">}</span> <span class="nd">@SuppressWarnings</span><span class="o">(</span><span class="s">"unchecked"</span><span class="o">)</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="no">V</span> <span class="nf">get</span><span class="o">()</span> <span class="o">{</span> <span class="nc">Cache</span><span class="o">.</span><span class="na">ValueWrapper</span> <span class="n">valueWrapper</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">adapter</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span> <span class="k">if</span> <span class="o">(</span><span class="n">valueWrapper</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="o">(</span><span class="no">V</span><span class="o">)</span><span class="n">valueWrapper</span><span class="o">.</span><span class="na">get</span><span class="o">();</span> <span class="o">}</span> <span class="k">return</span> <span class="kc">null</span><span class="o">;</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="no">V</span> <span class="nf">get</span><span class="o">(</span><span class="nc">Callable</span><span class="o">&lt;</span><span class="no">V</span><span class="o">&gt;</span> <span class="n">valueLoader</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">adapter</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">getName</span><span class="o">(),</span> <span class="n">valueLoader</span><span class="o">);</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">put</span><span class="o">(</span><span class="no">V</span> <span class="n">value</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">adapter</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">getName</span><span class="o">(),</span> <span class="n">value</span><span class="o">);</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">clear</span><span class="o">()</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">adapter</span><span class="o">.</span><span class="na">clear</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div> </div> </li> <li> <p>定义缓存提供者接口,用于提供底层实现</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">CacheProvider</span> <span class="o">{</span> <span class="cm">/** * 获取缓存 * * @param cacheBuilder 缓存构建器 * @return 缓存 */</span> <span class="o">&lt;</span><span class="no">K</span><span class="o">,</span> <span class="no">V</span><span class="o">&gt;</span> <span class="nc">Cache</span> <span class="nf">getCache</span><span class="o">(</span><span class="nc">CacheBuilder</span><span class="o">&lt;</span><span class="no">K</span><span class="o">,</span> <span class="no">V</span><span class="o">&gt;</span> <span class="n">cacheBuilder</span><span class="o">);</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div> </div> </li> <li> <p>缓存提供者具体实现,根据配置开启指定实现(本地缓存/分布式缓存)</p> <ul> <li> <p>本地缓存,采用 Caffeign</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">class</span> <span class="nc">MemoryCacheProvider</span> <span class="kd">implements</span> <span class="nc">CacheProvider</span> <span class="o">{</span> <span class="kd">private</span> <span class="kd">final</span> <span class="nc">VillaCacheProperties</span> <span class="n">cacheProperties</span><span class="o">;</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="o">&lt;</span><span class="no">K</span><span class="o">,</span> <span class="no">V</span><span class="o">&gt;</span> <span class="nc">Cache</span> <span class="nf">getCache</span><span class="o">(</span><span class="nc">CacheBuilder</span><span class="o">&lt;</span><span class="no">K</span><span class="o">,</span> <span class="no">V</span><span class="o">&gt;</span> <span class="n">cacheBuilder</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Caffeine</span><span class="o">&lt;</span><span class="nc">Object</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">newedBuilder</span> <span class="o">=</span> <span class="nc">Caffeine</span><span class="o">.</span><span class="na">newBuilder</span><span class="o">();</span> <span class="nc">String</span> <span class="n">name</span> <span class="o">=</span> <span class="n">cacheBuilder</span><span class="o">.</span><span class="na">getName</span><span class="o">();</span> <span class="kt">boolean</span> <span class="n">isConfig</span> <span class="o">=</span> <span class="n">cacheProperties</span><span class="o">.</span><span class="na">getNameConfigs</span><span class="o">().</span><span class="na">containsKey</span><span class="o">(</span><span class="n">name</span><span class="o">);</span> <span class="kt">boolean</span> <span class="n">cacheNullValues</span> <span class="o">=</span> <span class="n">cacheBuilder</span><span class="o">.</span><span class="na">isCacheNullValues</span><span class="o">();</span> <span class="nc">Duration</span> <span class="n">ttl</span> <span class="o">=</span> <span class="n">cacheBuilder</span><span class="o">.</span><span class="na">getTtl</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">isConfig</span><span class="o">)</span> <span class="o">{</span> <span class="nc">VillaCacheProperties</span><span class="o">.</span><span class="na">NameConfig</span> <span class="n">nameConfig</span> <span class="o">=</span> <span class="n">cacheProperties</span><span class="o">.</span><span class="na">getNameConfigs</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">name</span><span class="o">);</span> <span class="n">ttl</span> <span class="o">=</span> <span class="n">nameConfig</span><span class="o">.</span><span class="na">getTtl</span><span class="o">();</span> <span class="n">cacheNullValues</span> <span class="o">=</span> <span class="n">nameConfig</span><span class="o">.</span><span class="na">isCacheNullValues</span><span class="o">();</span> <span class="n">cacheBuilder</span><span class="o">.</span><span class="na">initializeClear</span><span class="o">(</span><span class="n">nameConfig</span><span class="o">.</span><span class="na">isInitializeClear</span><span class="o">());</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="n">ttl</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">newedBuilder</span><span class="o">.</span><span class="na">expireAfterWrite</span><span class="o">(</span><span class="n">ttl</span><span class="o">);</span> <span class="o">}</span> <span class="n">com</span><span class="o">.</span><span class="na">github</span><span class="o">.</span><span class="na">benmanes</span><span class="o">.</span><span class="na">caffeine</span><span class="o">.</span><span class="na">cache</span><span class="o">.</span><span class="na">Cache</span><span class="o">&lt;</span><span class="nc">Object</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">cache</span> <span class="o">=</span> <span class="n">newedBuilder</span><span class="o">.</span><span class="na">build</span><span class="o">();</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">CaffeineCache</span><span class="o">(</span><span class="n">name</span><span class="o">,</span> <span class="n">cache</span><span class="o">,</span> <span class="n">cacheNullValues</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div> </div> </li> <li> <p>分布式缓存,采用 Redis</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">class</span> <span class="nc">RedisCacheProvider</span> <span class="kd">implements</span> <span class="nc">CacheProvider</span> <span class="o">{</span> <span class="kd">private</span> <span class="kd">final</span> <span class="nc">RedisConnectionFactory</span> <span class="n">redisConnectionFactory</span><span class="o">;</span> <span class="kd">private</span> <span class="kd">final</span> <span class="nc">CacheProperties</span> <span class="n">cacheProperties</span><span class="o">;</span> <span class="kd">private</span> <span class="kd">final</span> <span class="nc">ResourceLoader</span> <span class="n">resourceLoader</span><span class="o">;</span> <span class="kd">private</span> <span class="kd">final</span> <span class="nc">VillaCacheProperties</span> <span class="n">villaCacheProperties</span><span class="o">;</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="o">&lt;</span><span class="no">K</span><span class="o">,</span> <span class="no">V</span><span class="o">&gt;</span> <span class="nc">Cache</span> <span class="nf">getCache</span><span class="o">(</span><span class="nc">CacheBuilder</span><span class="o">&lt;</span><span class="no">K</span><span class="o">,</span> <span class="no">V</span><span class="o">&gt;</span> <span class="n">cacheBuilder</span><span class="o">)</span> <span class="o">{</span> <span class="nc">String</span> <span class="n">name</span> <span class="o">=</span> <span class="n">cacheBuilder</span><span class="o">.</span><span class="na">getName</span><span class="o">();</span> <span class="nc">Duration</span> <span class="n">ttl</span> <span class="o">=</span> <span class="n">cacheBuilder</span><span class="o">.</span><span class="na">getTtl</span><span class="o">();</span> <span class="kt">boolean</span> <span class="n">cacheNullValues</span> <span class="o">=</span> <span class="n">cacheBuilder</span><span class="o">.</span><span class="na">isCacheNullValues</span><span class="o">();</span> <span class="kt">boolean</span> <span class="n">isConfig</span> <span class="o">=</span> <span class="n">villaCacheProperties</span><span class="o">.</span><span class="na">getNameConfigs</span><span class="o">().</span><span class="na">containsKey</span><span class="o">(</span><span class="n">name</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">isConfig</span><span class="o">)</span> <span class="o">{</span> <span class="nc">VillaCacheProperties</span><span class="o">.</span><span class="na">NameConfig</span> <span class="n">nameConfig</span> <span class="o">=</span> <span class="n">villaCacheProperties</span><span class="o">.</span><span class="na">getNameConfigs</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="n">name</span><span class="o">);</span> <span class="n">ttl</span> <span class="o">=</span> <span class="n">nameConfig</span><span class="o">.</span><span class="na">getTtl</span><span class="o">();</span> <span class="n">cacheNullValues</span> <span class="o">=</span> <span class="n">nameConfig</span><span class="o">.</span><span class="na">isCacheNullValues</span><span class="o">();</span> <span class="n">cacheBuilder</span><span class="o">.</span><span class="na">initializeClear</span><span class="o">(</span><span class="n">nameConfig</span><span class="o">.</span><span class="na">isInitializeClear</span><span class="o">());</span> <span class="o">}</span> <span class="nc">RedisCacheConfiguration</span> <span class="n">redisCacheConfiguration</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">createConfiguration</span><span class="o">(</span><span class="n">cacheProperties</span><span class="o">,</span> <span class="n">resourceLoader</span><span class="o">.</span><span class="na">getClassLoader</span><span class="o">());</span> <span class="k">if</span> <span class="o">(!</span><span class="n">cacheNullValues</span><span class="o">)</span> <span class="o">{</span> <span class="n">redisCacheConfiguration</span> <span class="o">=</span> <span class="n">redisCacheConfiguration</span><span class="o">.</span><span class="na">disableCachingNullValues</span><span class="o">();</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="n">ttl</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">redisCacheConfiguration</span> <span class="o">=</span> <span class="n">redisCacheConfiguration</span><span class="o">.</span><span class="na">entryTtl</span><span class="o">(</span><span class="n">ttl</span><span class="o">);</span> <span class="o">}</span> <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">createRedisCache</span><span class="o">(</span><span class="n">name</span><span class="o">,</span> <span class="n">redisCacheConfiguration</span><span class="o">);</span> <span class="o">}</span> <span class="kd">protected</span> <span class="nc">RedisCache</span> <span class="nf">createRedisCache</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="nd">@Nullable</span> <span class="nc">RedisCacheConfiguration</span> <span class="n">cacheConfig</span><span class="o">)</span> <span class="o">{</span> <span class="nc">RedisCacheManager</span><span class="o">.</span><span class="na">RedisCacheManagerBuilder</span> <span class="n">builder</span> <span class="o">=</span> <span class="nc">RedisCacheManager</span><span class="o">.</span><span class="na">builder</span><span class="o">(</span><span class="n">redisConnectionFactory</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">cacheConfig</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">builder</span><span class="o">.</span><span class="na">cacheDefaults</span><span class="o">(</span><span class="n">cacheConfig</span><span class="o">);</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="n">cacheProperties</span><span class="o">.</span><span class="na">getRedis</span><span class="o">().</span><span class="na">isEnableStatistics</span><span class="o">())</span> <span class="o">{</span> <span class="n">builder</span><span class="o">.</span><span class="na">enableStatistics</span><span class="o">();</span> <span class="o">}</span> <span class="k">return</span> <span class="o">(</span><span class="nc">RedisCache</span><span class="o">)</span><span class="n">builder</span><span class="o">.</span><span class="na">build</span><span class="o">().</span><span class="na">getCache</span><span class="o">(</span><span class="n">name</span><span class="o">);</span> <span class="o">}</span> <span class="kd">private</span> <span class="n">org</span><span class="o">.</span><span class="na">springframework</span><span class="o">.</span><span class="na">data</span><span class="o">.</span><span class="na">redis</span><span class="o">.</span><span class="na">cache</span><span class="o">.</span><span class="na">RedisCacheConfiguration</span> <span class="nf">createConfiguration</span><span class="o">(</span> <span class="nc">CacheProperties</span> <span class="n">cacheProperties</span><span class="o">,</span> <span class="nc">ClassLoader</span> <span class="n">classLoader</span><span class="o">)</span> <span class="o">{</span> <span class="nc">CacheProperties</span><span class="o">.</span><span class="na">Redis</span> <span class="n">redisProperties</span> <span class="o">=</span> <span class="n">cacheProperties</span><span class="o">.</span><span class="na">getRedis</span><span class="o">();</span> <span class="n">org</span><span class="o">.</span><span class="na">springframework</span><span class="o">.</span><span class="na">data</span><span class="o">.</span><span class="na">redis</span><span class="o">.</span><span class="na">cache</span><span class="o">.</span><span class="na">RedisCacheConfiguration</span> <span class="n">config</span> <span class="o">=</span> <span class="n">org</span><span class="o">.</span><span class="na">springframework</span><span class="o">.</span><span class="na">data</span><span class="o">.</span><span class="na">redis</span><span class="o">.</span><span class="na">cache</span><span class="o">.</span><span class="na">RedisCacheConfiguration</span> <span class="o">.</span><span class="na">defaultCacheConfig</span><span class="o">();</span> <span class="n">config</span> <span class="o">=</span> <span class="n">config</span> <span class="o">.</span><span class="na">serializeValuesWith</span><span class="o">(</span><span class="nc">RedisSerializationContext</span><span class="o">.</span><span class="na">SerializationPair</span><span class="o">.</span><span class="na">fromSerializer</span><span class="o">(</span><span class="k">new</span> <span class="nc">JdkSerializationRedisSerializer</span><span class="o">(</span><span class="n">classLoader</span><span class="o">)));</span> <span class="k">if</span> <span class="o">(</span><span class="n">redisProperties</span><span class="o">.</span><span class="na">getTimeToLive</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">config</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="na">entryTtl</span><span class="o">(</span><span class="n">redisProperties</span><span class="o">.</span><span class="na">getTimeToLive</span><span class="o">());</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="n">redisProperties</span><span class="o">.</span><span class="na">getKeyPrefix</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">config</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="na">prefixCacheNameWith</span><span class="o">(</span><span class="n">redisProperties</span><span class="o">.</span><span class="na">getKeyPrefix</span><span class="o">());</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(!</span><span class="n">redisProperties</span><span class="o">.</span><span class="na">isCacheNullValues</span><span class="o">())</span> <span class="o">{</span> <span class="n">config</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="na">disableCachingNullValues</span><span class="o">();</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(!</span><span class="n">redisProperties</span><span class="o">.</span><span class="na">isUseKeyPrefix</span><span class="o">())</span> <span class="o">{</span> <span class="n">config</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="na">disableKeyPrefix</span><span class="o">();</span> <span class="o">}</span> <span class="k">return</span> <span class="n">config</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div> </div> </li> </ul> </li> <li> <p>定义构建器,用于构建缓存</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">class</span> <span class="nc">CacheBuilder</span><span class="o">&lt;</span><span class="no">K</span><span class="o">,</span> <span class="no">V</span><span class="o">&gt;</span> <span class="o">{</span> <span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span> <span class="kd">private</span> <span class="nc">Duration</span> <span class="n">ttl</span><span class="o">;</span> <span class="kd">private</span> <span class="kt">boolean</span> <span class="n">cacheNullValues</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="kd">private</span> <span class="kt">boolean</span> <span class="n">initializeClear</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="kd">public</span> <span class="kd">static</span> <span class="nc">CacheBuilder</span><span class="o">&lt;</span><span class="nc">Object</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="nf">newBuilder</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nc">CacheBuilder</span><span class="o">&lt;&gt;();</span> <span class="o">}</span> <span class="cm">/** * 缓存名称 * * @param name 缓存名称 * @return this */</span> <span class="kd">public</span> <span class="nc">CacheBuilder</span><span class="o">&lt;</span><span class="no">K</span><span class="o">,</span> <span class="no">V</span><span class="o">&gt;</span> <span class="nf">name</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span> <span class="k">return</span> <span class="k">this</span><span class="o">;</span> <span class="o">}</span> <span class="cm">/** * 初始化时清理数据 * * @return this */</span> <span class="kd">public</span> <span class="nc">CacheBuilder</span><span class="o">&lt;</span><span class="no">K</span><span class="o">,</span> <span class="no">V</span><span class="o">&gt;</span> <span class="nf">initializeClear</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">initializeClear</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">initializeClear</span> <span class="o">=</span> <span class="n">initializeClear</span><span class="o">;</span> <span class="k">return</span> <span class="k">this</span><span class="o">;</span> <span class="o">}</span> <span class="cm">/** * 设置超时时间,超时自动剔除 * * @param ttl 超时时间 * @return this */</span> <span class="kd">public</span> <span class="nc">CacheBuilder</span><span class="o">&lt;</span><span class="no">K</span><span class="o">,</span> <span class="no">V</span><span class="o">&gt;</span> <span class="nf">ttl</span><span class="o">(</span><span class="nc">Duration</span> <span class="n">ttl</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">ttl</span> <span class="o">=</span> <span class="n">ttl</span><span class="o">;</span> <span class="k">return</span> <span class="k">this</span><span class="o">;</span> <span class="o">}</span> <span class="cm">/** * 缓存null值 * * @param cacheNullValues 是否允许缓存空值 * @return this */</span> <span class="kd">public</span> <span class="nc">CacheBuilder</span><span class="o">&lt;</span><span class="no">K</span><span class="o">,</span> <span class="no">V</span><span class="o">&gt;</span> <span class="nf">cacheNullValues</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">cacheNullValues</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">cacheNullValues</span> <span class="o">=</span> <span class="n">cacheNullValues</span><span class="o">;</span> <span class="k">return</span> <span class="k">this</span><span class="o">;</span> <span class="o">}</span> <span class="cm">/** * 构建缓存对象,Map结构 * * @param cacheProvider 缓存提供者 * @return 缓存对象,Map结构 */</span> <span class="kd">public</span> <span class="o">&lt;</span><span class="no">K1</span> <span class="kd">extends</span> <span class="no">K</span><span class="o">,</span> <span class="no">V1</span> <span class="kd">extends</span> <span class="no">V</span><span class="o">&gt;</span> <span class="nc">AdaptingCache</span><span class="o">&lt;</span><span class="no">K1</span><span class="o">,</span> <span class="no">V1</span><span class="o">&gt;</span> <span class="nf">build</span><span class="o">(</span><span class="nc">CacheProvider</span> <span class="n">cacheProvider</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Assert</span><span class="o">.</span><span class="na">state</span><span class="o">(</span><span class="n">name</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">,</span> <span class="s">"CacheBuild name must not be null."</span><span class="o">);</span> <span class="nc">AdaptingCache</span><span class="o">&lt;</span><span class="no">K1</span><span class="o">,</span> <span class="no">V1</span><span class="o">&gt;</span> <span class="n">cache</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">AbstractAdaptingCache</span><span class="o">&lt;</span><span class="no">K1</span><span class="o">,</span> <span class="no">V1</span><span class="o">&gt;(</span><span class="n">cacheProvider</span><span class="o">.</span><span class="na">getCache</span><span class="o">(</span><span class="k">this</span><span class="o">))</span> <span class="o">{</span> <span class="o">};</span> <span class="k">if</span> <span class="o">(</span><span class="nc">BooleanUtils</span><span class="o">.</span><span class="na">isTrue</span><span class="o">(</span><span class="n">initializeClear</span><span class="o">))</span> <span class="o">{</span> <span class="n">cache</span><span class="o">.</span><span class="na">clear</span><span class="o">();</span> <span class="o">}</span> <span class="k">return</span> <span class="n">cache</span><span class="o">;</span> <span class="o">}</span> <span class="cm">/** * 构建缓存对象,Value结构 * * @param cacheProvider 缓存提供者 * @return 缓存对象,Value结构 */</span> <span class="kd">public</span> <span class="o">&lt;</span><span class="no">V1</span> <span class="kd">extends</span> <span class="no">V</span><span class="o">&gt;</span> <span class="nc">AdaptingValueCache</span><span class="o">&lt;</span><span class="no">V1</span><span class="o">&gt;</span> <span class="nf">buildValue</span><span class="o">(</span><span class="nc">CacheProvider</span> <span class="n">cacheProvider</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Assert</span><span class="o">.</span><span class="na">state</span><span class="o">(</span><span class="n">name</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">,</span> <span class="s">"CacheBuild name must not be null."</span><span class="o">);</span> <span class="nc">AdaptingValueCache</span><span class="o">&lt;</span><span class="no">V1</span><span class="o">&gt;</span> <span class="n">valueCache</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">AbstractAdaptingValueCache</span><span class="o">&lt;</span><span class="no">V1</span><span class="o">&gt;(</span><span class="n">cacheProvider</span><span class="o">.</span><span class="na">getCache</span><span class="o">(</span><span class="k">this</span><span class="o">))</span> <span class="o">{</span> <span class="o">};</span> <span class="k">if</span> <span class="o">(</span><span class="nc">BooleanUtils</span><span class="o">.</span><span class="na">isTrue</span><span class="o">(</span><span class="n">initializeClear</span><span class="o">))</span> <span class="o">{</span> <span class="n">valueCache</span><span class="o">.</span><span class="na">clear</span><span class="o">();</span> <span class="o">}</span> <span class="k">return</span> <span class="n">valueCache</span><span class="o">;</span> <span class="o">}</span> <span class="cm">/** * 获取缓存名称 * * @return 缓存名称 */</span> <span class="kd">public</span> <span class="nc">String</span> <span class="nf">getName</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">name</span><span class="o">;</span> <span class="o">}</span> <span class="cm">/** * 获取缓存超时时间 * * @return 超时时间 */</span> <span class="kd">public</span> <span class="nc">Duration</span> <span class="nf">getTtl</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">ttl</span><span class="o">;</span> <span class="o">}</span> <span class="cm">/** * 是否允许缓存空值 * * @return 允许则为true */</span> <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">isCacheNullValues</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="n">cacheNullValues</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div> </div> </li> </ol> <h1 id="示例">示例</h1> <p><strong>改造前</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 </pre></td><td class="rouge-code"><pre><span class="kd">class</span> <span class="nc">Main</span><span class="o">{</span> <span class="kd">private</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">cache</span><span class="o">;</span> <span class="kd">private</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">values</span><span class="o">;</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p><strong>改造后</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 </pre></td><td class="rouge-code"><pre><span class="kd">class</span> <span class="nc">Main</span><span class="o">{</span> <span class="kd">private</span> <span class="nc">AdaptingCache</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">cache</span><span class="o">;</span> <span class="kd">private</span> <span class="nc">AdaptingValueCache</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">[]&gt;</span> <span class="n">values</span><span class="o">;</span> <span class="kd">public</span> <span class="nf">Main</span><span class="o">(</span><span class="nc">CacheProvider</span> <span class="n">cacheProvider</span><span class="o">){</span> <span class="n">cache</span> <span class="o">=</span> <span class="nc">CacheBuilder</span><span class="o">.</span><span class="na">newBuilder</span><span class="o">().</span><span class="na">name</span><span class="o">(</span><span class="s">"cache"</span><span class="o">).</span><span class="na">build</span><span class="o">(</span><span class="n">cacheProvider</span><span class="o">);</span> <span class="n">values</span> <span class="o">=</span><span class="nc">CacheBuilder</span><span class="o">.</span><span class="na">newBuilder</span><span class="o">().</span><span class="na">name</span><span class="o">(</span><span class="s">"values"</span><span class="o">).</span><span class="na">buildValue</span><span class="o">(</span><span class="n">cacheProvider</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <h1 id="拓展">拓展</h1> <p>架构设计没有统一的标准,每个人都有自己的设计理念。</p> <p>但奔向的目标是一致的,让产品更方便的使用、让后期维护和拓展更加容易…</p> <p>如果你有什么新的想法,评论区见</p>

2024/3/23
阅读更多

Spring Bean加载优先级

<p>当我们使用 <code class="language-plaintext highlighter-rouge">@ConditionalOnMissingBean</code> / <code class="language-plaintext highlighter-rouge">@ConditionalOnBean</code>注解去给某个 bean 注入赋予条件时,那在条件判断时我们需要确保条件判断过程所需的环境已准备好。</p> <p><strong>举个例子</strong></p> <p>下面的代码中有两个配置类,涉及两个 Bean 的注入</p> <p>配置类 ConfigA 需要注入一个 A,注入 A 没有条件,直接注入</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 </pre></td><td class="rouge-code"><pre><span class="kd">class</span> <span class="nc">ConfigA</span> <span class="o">{</span> <span class="nd">@Bean</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"a"</span><span class="o">)</span> <span class="kd">public</span> <span class="no">A</span> <span class="nf">a</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">A</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>配置类 ConfigB 需要注入一个 B,注入 B 有条件,只有当容器中没有 A 时才符合注入条件并进行注入</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 </pre></td><td class="rouge-code"><pre><span class="kd">class</span> <span class="nc">ConfigB</span> <span class="o">{</span> <span class="nd">@Bean</span> <span class="nd">@ConditionalOnMissingBean</span><span class="o">(</span><span class="n">name</span> <span class="o">=</span> <span class="s">"a"</span><span class="o">)</span> <span class="kd">public</span> <span class="no">B</span> <span class="nf">b</span><span class="o">()</span> <span class="o">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">B</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>这时候就会产生一个疑问?</p> <p>注入 B 的时候,Spring 是如何知道容器中是否已经存在 A 了。。。</p> <p><strong>结论</strong></p> <p>如果 ConfigA 和 ConfigB 都是用 @Configuration 标注的普通配置类,那注入 B 的条件判断 <code class="language-plaintext highlighter-rouge">@ConditionalOnMissingBean</code> 结果是不确定的。</p> <h1 id="原理分析">原理分析</h1> <blockquote> <p>Spring 配置类是有加载顺序的,Spring是根据加载顺序去判断容器中是否存在指定Bean</p> </blockquote> <h2 id="总的时间线">总的时间线</h2> <p>总的时间线共分为三部分</p> <ol> <li>加载所有的@Component类,自然包括(@Service、@Controller、@Configuration…),并有序保存</li> <li>按照上述保存的有序集合,遍历每一个@Component类中的每一个method,识别@Bean,注册 BeanDefinition</li> <li>遍历所有的BeanDefinition,进行实例化</li> </ol> <p>从上述时间线可以看到,解析 @ConditionalOnMissingBean 注解发生在时间线的第二部分。</p> <p>在扫描到 @ConditionalOnMissingBean 注解时,会判断当前条件是否满足,满足则对当前 Bean 进行注册,而判断满足的条件就是根据注册保存的 <code class="language-plaintext highlighter-rouge">BeanDefinition</code> 集合去判断的。</p> <p>因此,如果当前条件所依赖的 Bean 已经被扫描过了,并保存到了 BeanDefinition 集合中,那 Spring 就能正确的判断到 Bean 的存在。若当前条件所依赖的 Bean 还没有被扫描到,那 BeanDefinition 集合中就找不到,就会产生误判的情况,造成程序错误。</p> <p>所以,@ConditionalOnMissingBean 要想正确被判断,我们就需要保证类的顺序性,保证当前依赖的 Bean 所在的类必须在本类之前被加载,而保证类的加载顺序,这就是第一部分做的事情。</p> <h2 id="时间线细粒度分析">时间线细粒度分析</h2> <h3 id="1-加载component类">1. 加载@Component类</h3> <blockquote> <p>上述分析中最重要的就是这部分,只要这部分的顺序性保证好了,那后续就不能出问题</p> </blockquote> <p>这块代码的入口在 <code class="language-plaintext highlighter-rouge">org.springframework.context.annotation.ConfigurationClassParser::parse</code></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kt">void</span> <span class="nf">parse</span><span class="o">(</span><span class="nc">Set</span><span class="o">&lt;</span><span class="nc">BeanDefinitionHolder</span><span class="o">&gt;</span> <span class="n">configCandidates</span><span class="o">)</span> <span class="o">{</span> <span class="k">for</span> <span class="o">(</span><span class="nc">BeanDefinitionHolder</span> <span class="n">holder</span> <span class="o">:</span> <span class="n">configCandidates</span><span class="o">)</span> <span class="o">{</span> <span class="nc">BeanDefinition</span> <span class="n">bd</span> <span class="o">=</span> <span class="n">holder</span><span class="o">.</span><span class="na">getBeanDefinition</span><span class="o">();</span> <span class="k">try</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">bd</span> <span class="k">instanceof</span> <span class="nc">AnnotatedBeanDefinition</span><span class="o">)</span> <span class="o">{</span> <span class="n">parse</span><span class="o">(((</span><span class="nc">AnnotatedBeanDefinition</span><span class="o">)</span> <span class="n">bd</span><span class="o">).</span><span class="na">getMetadata</span><span class="o">(),</span> <span class="n">holder</span><span class="o">.</span><span class="na">getBeanName</span><span class="o">());</span> <span class="o">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="o">(</span><span class="n">bd</span> <span class="k">instanceof</span> <span class="nc">AbstractBeanDefinition</span> <span class="o">&amp;&amp;</span> <span class="o">((</span><span class="nc">AbstractBeanDefinition</span><span class="o">)</span> <span class="n">bd</span><span class="o">).</span><span class="na">hasBeanClass</span><span class="o">())</span> <span class="o">{</span> <span class="n">parse</span><span class="o">(((</span><span class="nc">AbstractBeanDefinition</span><span class="o">)</span> <span class="n">bd</span><span class="o">).</span><span class="na">getBeanClass</span><span class="o">(),</span> <span class="n">holder</span><span class="o">.</span><span class="na">getBeanName</span><span class="o">());</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="n">parse</span><span class="o">(</span><span class="n">bd</span><span class="o">.</span><span class="na">getBeanClassName</span><span class="o">(),</span> <span class="n">holder</span><span class="o">.</span><span class="na">getBeanName</span><span class="o">());</span> <span class="o">}</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">BeanDefinitionStoreException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="n">ex</span><span class="o">;</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Throwable</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">BeanDefinitionStoreException</span><span class="o">(</span> <span class="s">"Failed to parse configuration class ["</span> <span class="o">+</span> <span class="n">bd</span><span class="o">.</span><span class="na">getBeanClassName</span><span class="o">()</span> <span class="o">+</span> <span class="s">"]"</span><span class="o">,</span> <span class="n">ex</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="k">this</span><span class="o">.</span><span class="na">deferredImportSelectorHandler</span><span class="o">.</span><span class="na">process</span><span class="o">();</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>上述入口的逻辑可以看到,又分为了两部分,一部分是 parse,另一部分是 deferredImportSelectorHandler.process</p> <h4 id="步骤一parse">步骤一:parse</h4> <p>下述代码就是 parse 做的事情,第一个进来的类是启动类。</p> <p>有<code class="language-plaintext highlighter-rouge">3</code>个重要的全局变量</p> <ul> <li>configurationClasses,使用的是有序Map,这块保存的就是加载的@Component类,具有有序性,因此我们其实保证的就是这个集合中类的顺序,时间线第二部分就是有序遍历这个集合进行的。</li> <li>deferredImportSelectorHandler,和入口的第二部分有关。</li> <li>importStack,这是扫描的精髓,基于栈的形式递归当前入口</li> </ul> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 </pre></td><td class="rouge-code"><pre><span class="kd">private</span> <span class="kd">final</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">ConfigurationClass</span><span class="o">,</span> <span class="nc">ConfigurationClass</span><span class="o">&gt;</span> <span class="n">configurationClasses</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">LinkedHashMap</span><span class="o">&lt;&gt;();</span> <span class="kd">private</span> <span class="kd">final</span> <span class="nc">DeferredImportSelectorHandler</span> <span class="n">deferredImportSelectorHandler</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DeferredImportSelectorHandler</span><span class="o">();</span> <span class="kd">private</span> <span class="kd">final</span> <span class="nc">ImportStack</span> <span class="n">importStack</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ImportStack</span><span class="o">();</span> <span class="kd">protected</span> <span class="kd">final</span> <span class="nc">SourceClass</span> <span class="nf">doProcessConfigurationClass</span><span class="o">(</span> <span class="nc">ConfigurationClass</span> <span class="n">configClass</span><span class="o">,</span> <span class="nc">SourceClass</span> <span class="n">sourceClass</span><span class="o">,</span> <span class="nc">Predicate</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">filter</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">configClass</span><span class="o">.</span><span class="na">getMetadata</span><span class="o">().</span><span class="na">isAnnotated</span><span class="o">(</span><span class="nc">Component</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">()))</span> <span class="o">{</span> <span class="c1">// Recursively process any member (nested) classes first</span> <span class="n">processMemberClasses</span><span class="o">(</span><span class="n">configClass</span><span class="o">,</span> <span class="n">sourceClass</span><span class="o">,</span> <span class="n">filter</span><span class="o">);</span> <span class="o">}</span> <span class="c1">// Process any @PropertySource annotations</span> <span class="k">for</span> <span class="o">(</span><span class="nc">AnnotationAttributes</span> <span class="n">propertySource</span> <span class="o">:</span> <span class="nc">AnnotationConfigUtils</span><span class="o">.</span><span class="na">attributesForRepeatable</span><span class="o">(</span> <span class="n">sourceClass</span><span class="o">.</span><span class="na">getMetadata</span><span class="o">(),</span> <span class="nc">PropertySources</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">org</span><span class="o">.</span><span class="na">springframework</span><span class="o">.</span><span class="na">context</span><span class="o">.</span><span class="na">annotation</span><span class="o">.</span><span class="na">PropertySource</span><span class="o">.</span><span class="na">class</span><span class="o">))</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">environment</span> <span class="k">instanceof</span> <span class="nc">ConfigurableEnvironment</span><span class="o">)</span> <span class="o">{</span> <span class="n">processPropertySource</span><span class="o">(</span><span class="n">propertySource</span><span class="o">);</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="n">logger</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"Ignoring @PropertySource annotation on ["</span> <span class="o">+</span> <span class="n">sourceClass</span><span class="o">.</span><span class="na">getMetadata</span><span class="o">().</span><span class="na">getClassName</span><span class="o">()</span> <span class="o">+</span> <span class="s">"]. Reason: Environment must implement ConfigurableEnvironment"</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="c1">// Process any @ComponentScan annotations</span> <span class="nc">Set</span><span class="o">&lt;</span><span class="nc">AnnotationAttributes</span><span class="o">&gt;</span> <span class="n">componentScans</span> <span class="o">=</span> <span class="nc">AnnotationConfigUtils</span><span class="o">.</span><span class="na">attributesForRepeatable</span><span class="o">(</span> <span class="n">sourceClass</span><span class="o">.</span><span class="na">getMetadata</span><span class="o">(),</span> <span class="nc">ComponentScans</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">ComponentScan</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="k">if</span> <span class="o">(!</span><span class="n">componentScans</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">()</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="k">this</span><span class="o">.</span><span class="na">conditionEvaluator</span><span class="o">.</span><span class="na">shouldSkip</span><span class="o">(</span><span class="n">sourceClass</span><span class="o">.</span><span class="na">getMetadata</span><span class="o">(),</span> <span class="nc">ConfigurationPhase</span><span class="o">.</span><span class="na">REGISTER_BEAN</span><span class="o">))</span> <span class="o">{</span> <span class="k">for</span> <span class="o">(</span><span class="nc">AnnotationAttributes</span> <span class="n">componentScan</span> <span class="o">:</span> <span class="n">componentScans</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// The config class is annotated with @ComponentScan -&gt; perform the scan immediately</span> <span class="nc">Set</span><span class="o">&lt;</span><span class="nc">BeanDefinitionHolder</span><span class="o">&gt;</span> <span class="n">scannedBeanDefinitions</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">componentScanParser</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="n">componentScan</span><span class="o">,</span> <span class="n">sourceClass</span><span class="o">.</span><span class="na">getMetadata</span><span class="o">().</span><span class="na">getClassName</span><span class="o">());</span> <span class="c1">// Check the set of scanned definitions for any further config classes and parse recursively if needed</span> <span class="k">for</span> <span class="o">(</span><span class="nc">BeanDefinitionHolder</span> <span class="n">holder</span> <span class="o">:</span> <span class="n">scannedBeanDefinitions</span><span class="o">)</span> <span class="o">{</span> <span class="nc">BeanDefinition</span> <span class="n">bdCand</span> <span class="o">=</span> <span class="n">holder</span><span class="o">.</span><span class="na">getBeanDefinition</span><span class="o">().</span><span class="na">getOriginatingBeanDefinition</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">bdCand</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">bdCand</span> <span class="o">=</span> <span class="n">holder</span><span class="o">.</span><span class="na">getBeanDefinition</span><span class="o">();</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="nc">ConfigurationClassUtils</span><span class="o">.</span><span class="na">checkConfigurationClassCandidate</span><span class="o">(</span><span class="n">bdCand</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">metadataReaderFactory</span><span class="o">))</span> <span class="o">{</span> <span class="n">parse</span><span class="o">(</span><span class="n">bdCand</span><span class="o">.</span><span class="na">getBeanClassName</span><span class="o">(),</span> <span class="n">holder</span><span class="o">.</span><span class="na">getBeanName</span><span class="o">());</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> <span class="c1">// Process any @Import annotations</span> <span class="n">processImports</span><span class="o">(</span><span class="n">configClass</span><span class="o">,</span> <span class="n">sourceClass</span><span class="o">,</span> <span class="n">getImports</span><span class="o">(</span><span class="n">sourceClass</span><span class="o">),</span> <span class="n">filter</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span> <span class="c1">// Process any @ImportResource annotations</span> <span class="nc">AnnotationAttributes</span> <span class="n">importResource</span> <span class="o">=</span> <span class="nc">AnnotationConfigUtils</span><span class="o">.</span><span class="na">attributesFor</span><span class="o">(</span><span class="n">sourceClass</span><span class="o">.</span><span class="na">getMetadata</span><span class="o">(),</span> <span class="nc">ImportResource</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">importResource</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">resources</span> <span class="o">=</span> <span class="n">importResource</span><span class="o">.</span><span class="na">getStringArray</span><span class="o">(</span><span class="s">"locations"</span><span class="o">);</span> <span class="nc">Class</span><span class="o">&lt;?</span> <span class="kd">extends</span> <span class="nc">BeanDefinitionReader</span><span class="o">&gt;</span> <span class="n">readerClass</span> <span class="o">=</span> <span class="n">importResource</span><span class="o">.</span><span class="na">getClass</span><span class="o">(</span><span class="s">"reader"</span><span class="o">);</span> <span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="n">resource</span> <span class="o">:</span> <span class="n">resources</span><span class="o">)</span> <span class="o">{</span> <span class="nc">String</span> <span class="n">resolvedResource</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">environment</span><span class="o">.</span><span class="na">resolveRequiredPlaceholders</span><span class="o">(</span><span class="n">resource</span><span class="o">);</span> <span class="n">configClass</span><span class="o">.</span><span class="na">addImportedResource</span><span class="o">(</span><span class="n">resolvedResource</span><span class="o">,</span> <span class="n">readerClass</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="c1">// Process individual @Bean methods</span> <span class="nc">Set</span><span class="o">&lt;</span><span class="nc">MethodMetadata</span><span class="o">&gt;</span> <span class="n">beanMethods</span> <span class="o">=</span> <span class="n">retrieveBeanMethodMetadata</span><span class="o">(</span><span class="n">sourceClass</span><span class="o">);</span> <span class="k">for</span> <span class="o">(</span><span class="nc">MethodMetadata</span> <span class="n">methodMetadata</span> <span class="o">:</span> <span class="n">beanMethods</span><span class="o">)</span> <span class="o">{</span> <span class="n">configClass</span><span class="o">.</span><span class="na">addBeanMethod</span><span class="o">(</span><span class="k">new</span> <span class="nc">BeanMethod</span><span class="o">(</span><span class="n">methodMetadata</span><span class="o">,</span> <span class="n">configClass</span><span class="o">));</span> <span class="o">}</span> <span class="c1">// Process default methods on interfaces</span> <span class="n">processInterfaces</span><span class="o">(</span><span class="n">configClass</span><span class="o">,</span> <span class="n">sourceClass</span><span class="o">);</span> <span class="c1">// Process superclass, if any</span> <span class="k">if</span> <span class="o">(</span><span class="n">sourceClass</span><span class="o">.</span><span class="na">getMetadata</span><span class="o">().</span><span class="na">hasSuperClass</span><span class="o">())</span> <span class="o">{</span> <span class="nc">String</span> <span class="n">superclass</span> <span class="o">=</span> <span class="n">sourceClass</span><span class="o">.</span><span class="na">getMetadata</span><span class="o">().</span><span class="na">getSuperClassName</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">superclass</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">superclass</span><span class="o">.</span><span class="na">startsWith</span><span class="o">(</span><span class="s">"java"</span><span class="o">)</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="k">this</span><span class="o">.</span><span class="na">knownSuperclasses</span><span class="o">.</span><span class="na">containsKey</span><span class="o">(</span><span class="n">superclass</span><span class="o">))</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">knownSuperclasses</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">superclass</span><span class="o">,</span> <span class="n">configClass</span><span class="o">);</span> <span class="c1">// Superclass found, return its annotation metadata and recurse</span> <span class="k">return</span> <span class="n">sourceClass</span><span class="o">.</span><span class="na">getSuperClass</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="c1">// No superclass -&gt; processing is complete</span> <span class="k">return</span> <span class="kc">null</span><span class="o">;</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>上述逻辑比较多,简单概括做的事情</p> <ol> <li>扫描自身、父类是否用注解@Component标注,对扫描到的类递归执行当前操作</li> <li>扫描@PropertySource,并加载到Environment</li> <li>扫描@ComponentScan,并扫描涉及到相关路径的所有类,对扫描到的类递归执行当前操作</li> <li>扫描@Import,对扫描到的类递归执行当前操作</li> <li>扫描@ImportResource,并加载到Environment</li> </ol> <p>上述操作的每一步都会将扫描到的组件类添加到全局变量 configurationClasses 中。</p> <p>由于当前操作基于递归实现,因此上述操作的有序性是得不到保证的。</p> <p><strong>这块核心的是步骤四,扫描@Import</strong>,下述代码是扫描@Import的逻辑</p> <p>在看下述代码时可以先了解一下@Import的功能</p> <blockquote> <p>@Import注解可以导入三种类</p> <ul> <li> <p>普通类</p> </li> <li> <p>实现ImportSelector接口的类</p> <p>这个接口的主要作用是批量导入,实现接口 selectImports,可以看到返回值是一个数组,这里需要返回多个类的全限定类名,实现批量导入</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">ImportSelector</span> <span class="o">{</span> <span class="nc">String</span><span class="o">[]</span> <span class="nf">selectImports</span><span class="o">(</span><span class="nc">AnnotationMetadata</span> <span class="n">importingClassMetadata</span><span class="o">);</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div> </div> </li> <li> <p>实现ImportBeanDefinitionRegistrar接口的类</p> <p>这个接口的主要作用是提供 BeanDefinition 注册 Bean 的逻辑</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">ImportBeanDefinitionRegistrar</span> <span class="o">{</span> <span class="k">default</span> <span class="kt">void</span> <span class="nf">registerBeanDefinitions</span><span class="o">(</span><span class="nc">AnnotationMetadata</span> <span class="n">importingClassMetadata</span><span class="o">,</span> <span class="nc">BeanDefinitionRegistry</span> <span class="n">registry</span><span class="o">,</span> <span class="nc">BeanNameGenerator</span> <span class="n">importBeanNameGenerator</span><span class="o">)</span> <span class="o">{</span> <span class="n">registerBeanDefinitions</span><span class="o">(</span><span class="n">importingClassMetadata</span><span class="o">,</span> <span class="n">registry</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div> </div> </li> </ul> </blockquote> <p>了解了关于 @Import 的功能,就可以看下述代码了</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 </pre></td><td class="rouge-code"><pre> <span class="kd">private</span> <span class="kt">void</span> <span class="nf">processImports</span><span class="o">(</span><span class="nc">ConfigurationClass</span> <span class="n">configClass</span><span class="o">,</span> <span class="nc">SourceClass</span> <span class="n">currentSourceClass</span><span class="o">,</span> <span class="nc">Collection</span><span class="o">&lt;</span><span class="nc">SourceClass</span><span class="o">&gt;</span> <span class="n">importCandidates</span><span class="o">,</span> <span class="nc">Predicate</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">exclusionFilter</span><span class="o">,</span> <span class="kt">boolean</span> <span class="n">checkForCircularImports</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">importCandidates</span><span class="o">.</span><span class="na">isEmpty</span><span class="o">())</span> <span class="o">{</span> <span class="k">return</span><span class="o">;</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="n">checkForCircularImports</span> <span class="o">&amp;&amp;</span> <span class="n">isChainedImportOnStack</span><span class="o">(</span><span class="n">configClass</span><span class="o">))</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">problemReporter</span><span class="o">.</span><span class="na">error</span><span class="o">(</span><span class="k">new</span> <span class="nc">CircularImportProblem</span><span class="o">(</span><span class="n">configClass</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">importStack</span><span class="o">));</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">importStack</span><span class="o">.</span><span class="na">push</span><span class="o">(</span><span class="n">configClass</span><span class="o">);</span> <span class="k">try</span> <span class="o">{</span> <span class="k">for</span> <span class="o">(</span><span class="nc">SourceClass</span> <span class="n">candidate</span> <span class="o">:</span> <span class="n">importCandidates</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">candidate</span><span class="o">.</span><span class="na">isAssignable</span><span class="o">(</span><span class="nc">ImportSelector</span><span class="o">.</span><span class="na">class</span><span class="o">))</span> <span class="o">{</span> <span class="c1">// Candidate class is an ImportSelector -&gt; delegate to it to determine imports</span> <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">candidateClass</span> <span class="o">=</span> <span class="n">candidate</span><span class="o">.</span><span class="na">loadClass</span><span class="o">();</span> <span class="nc">ImportSelector</span> <span class="n">selector</span> <span class="o">=</span> <span class="nc">ParserStrategyUtils</span><span class="o">.</span><span class="na">instantiateClass</span><span class="o">(</span><span class="n">candidateClass</span><span class="o">,</span> <span class="nc">ImportSelector</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">environment</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">resourceLoader</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">registry</span><span class="o">);</span> <span class="nc">Predicate</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">selectorFilter</span> <span class="o">=</span> <span class="n">selector</span><span class="o">.</span><span class="na">getExclusionFilter</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="n">selectorFilter</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="n">exclusionFilter</span> <span class="o">=</span> <span class="n">exclusionFilter</span><span class="o">.</span><span class="na">or</span><span class="o">(</span><span class="n">selectorFilter</span><span class="o">);</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="n">selector</span> <span class="k">instanceof</span> <span class="nc">DeferredImportSelector</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">deferredImportSelectorHandler</span><span class="o">.</span><span class="na">handle</span><span class="o">(</span><span class="n">configClass</span><span class="o">,</span> <span class="o">(</span><span class="nc">DeferredImportSelector</span><span class="o">)</span> <span class="n">selector</span><span class="o">);</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">importClassNames</span> <span class="o">=</span> <span class="n">selector</span><span class="o">.</span><span class="na">selectImports</span><span class="o">(</span><span class="n">currentSourceClass</span><span class="o">.</span><span class="na">getMetadata</span><span class="o">());</span> <span class="nc">Collection</span><span class="o">&lt;</span><span class="nc">SourceClass</span><span class="o">&gt;</span> <span class="n">importSourceClasses</span> <span class="o">=</span> <span class="n">asSourceClasses</span><span class="o">(</span><span class="n">importClassNames</span><span class="o">,</span> <span class="n">exclusionFilter</span><span class="o">);</span> <span class="n">processImports</span><span class="o">(</span><span class="n">configClass</span><span class="o">,</span> <span class="n">currentSourceClass</span><span class="o">,</span> <span class="n">importSourceClasses</span><span class="o">,</span> <span class="n">exclusionFilter</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="o">(</span><span class="n">candidate</span><span class="o">.</span><span class="na">isAssignable</span><span class="o">(</span><span class="nc">ImportBeanDefinitionRegistrar</span><span class="o">.</span><span class="na">class</span><span class="o">))</span> <span class="o">{</span> <span class="c1">// Candidate class is an ImportBeanDefinitionRegistrar -&gt;</span> <span class="c1">// delegate to it to register additional bean definitions</span> <span class="nc">Class</span><span class="o">&lt;?&gt;</span> <span class="n">candidateClass</span> <span class="o">=</span> <span class="n">candidate</span><span class="o">.</span><span class="na">loadClass</span><span class="o">();</span> <span class="nc">ImportBeanDefinitionRegistrar</span> <span class="n">registrar</span> <span class="o">=</span> <span class="nc">ParserStrategyUtils</span><span class="o">.</span><span class="na">instantiateClass</span><span class="o">(</span><span class="n">candidateClass</span><span class="o">,</span> <span class="nc">ImportBeanDefinitionRegistrar</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">environment</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">resourceLoader</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">registry</span><span class="o">);</span> <span class="n">configClass</span><span class="o">.</span><span class="na">addImportBeanDefinitionRegistrar</span><span class="o">(</span><span class="n">registrar</span><span class="o">,</span> <span class="n">currentSourceClass</span><span class="o">.</span><span class="na">getMetadata</span><span class="o">());</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="c1">// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar -&gt;</span> <span class="c1">// process it as an @Configuration class</span> <span class="k">this</span><span class="o">.</span><span class="na">importStack</span><span class="o">.</span><span class="na">registerImport</span><span class="o">(</span> <span class="n">currentSourceClass</span><span class="o">.</span><span class="na">getMetadata</span><span class="o">(),</span> <span class="n">candidate</span><span class="o">.</span><span class="na">getMetadata</span><span class="o">().</span><span class="na">getClassName</span><span class="o">());</span> <span class="n">processConfigurationClass</span><span class="o">(</span><span class="n">candidate</span><span class="o">.</span><span class="na">asConfigClass</span><span class="o">(</span><span class="n">configClass</span><span class="o">),</span> <span class="n">exclusionFilter</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">BeanDefinitionStoreException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="n">ex</span><span class="o">;</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">Throwable</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span> <span class="k">throw</span> <span class="k">new</span> <span class="nf">BeanDefinitionStoreException</span><span class="o">(</span> <span class="s">"Failed to process import candidates for configuration class ["</span> <span class="o">+</span> <span class="n">configClass</span><span class="o">.</span><span class="na">getMetadata</span><span class="o">().</span><span class="na">getClassName</span><span class="o">()</span> <span class="o">+</span> <span class="s">"]"</span><span class="o">,</span> <span class="n">ex</span><span class="o">);</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">importStack</span><span class="o">.</span><span class="na">pop</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>上述代码逻辑简单整理:</p> <ol> <li> <p>判断要导入的类是否实现ImportSelector接口</p> <ul> <li> <p>判断是否实现DeferredImportSelector接口</p> <p><strong>这里就比较核心了,如果实现了DeferredImportSelector接口,则将当前的Selector注册到全局变量deferredImportSelectorHandler中。先不进行扫描</strong></p> </li> <li> <p>若不实现DeferredImportSelector接口,执行方法selectImports,将方法返回的数组中所有类都进行扫描</p> </li> </ul> </li> <li> <p>判断要导入的类是否实现ImportBeanDefinitionRegistrar接口</p> </li> <li> <p>上述2个接口都没有实现,那就是导入的普通类,递归执行前面的扫描流程</p> </li> </ol> <h4 id="步骤二deferredimportselectorhandler">步骤二:deferredImportSelectorHandler</h4> <p>通过步骤一可以得出,这里面保存的都是实现DeferredImportSelector接口的导入类。</p> <p>这块的逻辑也是对deferredImportSelectorHandler中保存的类进行扫描处理。</p> <p>那站在顺序性的角度考虑,这块逻辑中扫描的类的顺序肯定比步骤一的要晚。</p> <p><strong>可以得出结论,要想调整扫描顺序,让别的类在本类之前加载,那就可以让别的类在步骤一中加载,本类在步骤二中加载。</strong></p> <p>要想在步骤一中加载,需要用@Component修饰类</p> <p>要想在步骤二中加载,需要一个实现DeferredImportSelector接口的导入类,将本类的全限定类名进行返回。</p> <p><strong>这时候自动装配类的作用就凸显出来了</strong></p> <p>Spring 若要开启自动装配,需要使用注解@EnableAutoConfiguration去修饰。</p> <p>而注解@EnableAutoConfiguration又导入了一个AutoConfigurationImportSelector类,并且AutoConfigurationImportSelector类实现于DeferredImportSelector接口。</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 </pre></td><td class="rouge-code"><pre><span class="nd">@Import</span><span class="o">(</span><span class="nc">AutoConfigurationImportSelector</span><span class="o">.</span><span class="na">class</span><span class="o">)</span> <span class="kd">public</span> <span class="nd">@interface</span> <span class="nc">EnableAutoConfiguration</span> <span class="o">{</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">class</span> <span class="nc">AutoConfigurationImportSelector</span> <span class="kd">implements</span> <span class="nc">DeferredImportSelector</span><span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="nc">String</span><span class="o">[]</span> <span class="nf">selectImports</span><span class="o">(</span><span class="nc">AnnotationMetadata</span> <span class="n">annotationMetadata</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(!</span><span class="n">isEnabled</span><span class="o">(</span><span class="n">annotationMetadata</span><span class="o">))</span> <span class="o">{</span> <span class="k">return</span> <span class="no">NO_IMPORTS</span><span class="o">;</span> <span class="o">}</span> <span class="nc">AutoConfigurationEntry</span> <span class="n">autoConfigurationEntry</span> <span class="o">=</span> <span class="n">getAutoConfigurationEntry</span><span class="o">(</span><span class="n">annotationMetadata</span><span class="o">);</span> <span class="k">return</span> <span class="nc">StringUtils</span><span class="o">.</span><span class="na">toStringArray</span><span class="o">(</span><span class="n">autoConfigurationEntry</span><span class="o">.</span><span class="na">getConfigurations</span><span class="o">());</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>所有可以得出,自动装配类是在第二步骤中进行扫描的,<strong>自动装配类的加载顺序低于普通配置类</strong></p> <p>那要保证顺序,就可以让条件依赖的Bean使用普通配置类,本类使用自动装配类。</p> <p>如果条件依赖的Bean也是自动装配类,那就可使用自动装配类的特性,进行自动装配类之前的顺序调整,比如(@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder)。</p> <h3 id="2-扫描component类中每个method">2. 扫描@Component类中每个method</h3> <p>这块逻辑的入口是 <code class="language-plaintext highlighter-rouge">org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader::loadBeanDefinitions</code></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 </pre></td><td class="rouge-code"><pre><span class="kd">class</span> <span class="nc">ConfigurationClassBeanDefinitionReader</span> <span class="o">{</span> <span class="cm">/** * 入参就是时间线第一部分加载的组件类 */</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">loadBeanDefinitions</span><span class="o">(</span><span class="nc">Set</span><span class="o">&lt;</span><span class="nc">ConfigurationClass</span><span class="o">&gt;</span> <span class="n">configurationModel</span><span class="o">)</span> <span class="o">{</span> <span class="nc">TrackedConditionEvaluator</span> <span class="n">trackedConditionEvaluator</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">TrackedConditionEvaluator</span><span class="o">();</span> <span class="k">for</span> <span class="o">(</span><span class="nc">ConfigurationClass</span> <span class="n">configClass</span> <span class="o">:</span> <span class="n">configurationModel</span><span class="o">)</span> <span class="o">{</span> <span class="n">loadBeanDefinitionsForConfigurationClass</span><span class="o">(</span><span class="n">configClass</span><span class="o">,</span> <span class="n">trackedConditionEvaluator</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">private</span> <span class="kt">void</span> <span class="nf">loadBeanDefinitionsForConfigurationClass</span><span class="o">(</span> <span class="nc">ConfigurationClass</span> <span class="n">configClass</span><span class="o">,</span> <span class="nc">TrackedConditionEvaluator</span> <span class="n">trackedConditionEvaluator</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">trackedConditionEvaluator</span><span class="o">.</span><span class="na">shouldSkip</span><span class="o">(</span><span class="n">configClass</span><span class="o">))</span> <span class="o">{</span> <span class="nc">String</span> <span class="n">beanName</span> <span class="o">=</span> <span class="n">configClass</span><span class="o">.</span><span class="na">getBeanName</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="nc">StringUtils</span><span class="o">.</span><span class="na">hasLength</span><span class="o">(</span><span class="n">beanName</span><span class="o">)</span> <span class="o">&amp;&amp;</span> <span class="k">this</span><span class="o">.</span><span class="na">registry</span><span class="o">.</span><span class="na">containsBeanDefinition</span><span class="o">(</span><span class="n">beanName</span><span class="o">))</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">registry</span><span class="o">.</span><span class="na">removeBeanDefinition</span><span class="o">(</span><span class="n">beanName</span><span class="o">);</span> <span class="o">}</span> <span class="k">this</span><span class="o">.</span><span class="na">importRegistry</span><span class="o">.</span><span class="na">removeImportingClass</span><span class="o">(</span><span class="n">configClass</span><span class="o">.</span><span class="na">getMetadata</span><span class="o">().</span><span class="na">getClassName</span><span class="o">());</span> <span class="k">return</span><span class="o">;</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="n">configClass</span><span class="o">.</span><span class="na">isImported</span><span class="o">())</span> <span class="o">{</span> <span class="n">registerBeanDefinitionForImportedConfigurationClass</span><span class="o">(</span><span class="n">configClass</span><span class="o">);</span> <span class="o">}</span> <span class="k">for</span> <span class="o">(</span><span class="nc">BeanMethod</span> <span class="n">beanMethod</span> <span class="o">:</span> <span class="n">configClass</span><span class="o">.</span><span class="na">getBeanMethods</span><span class="o">())</span> <span class="o">{</span> <span class="n">loadBeanDefinitionsForBeanMethod</span><span class="o">(</span><span class="n">beanMethod</span><span class="o">);</span> <span class="o">}</span> <span class="n">loadBeanDefinitionsFromImportedResources</span><span class="o">(</span><span class="n">configClass</span><span class="o">.</span><span class="na">getImportedResources</span><span class="o">());</span> <span class="n">loadBeanDefinitionsFromRegistrars</span><span class="o">(</span><span class="n">configClass</span><span class="o">.</span><span class="na">getImportBeanDefinitionRegistrars</span><span class="o">());</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>这块的逻辑主要有以下部分:</p> <ol> <li>判断当前类是否需要跳过,依赖于类上的条件,比如我们经常在类上添加条件@ConditionalOnClass、@ConditionalOnProperty……</li> <li>是否是导入类,将导入类本身注册为BeanDefinition</li> <li>遍历每一个method,扫描判断每一个method所修饰的注解,比如@Bean、@Autowire、@Scope、@ConditionalOnMissingBean…..并将当前Bean注册为BeanDefinition。</li> </ol> <p>注册到BeanDefinition时保存的位置是BeanDefinitionRegistry的默认实现 <code class="language-plaintext highlighter-rouge">DefaultListableBeanFactory</code>中的全局变量beanDefinitionMap中,@Conditional条件判断就是判断这里面有没有目标Bean,若加载的比较晚,那肯定找不到,这也是场景中导致问题的根本原因。</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">class</span> <span class="nc">DefaultListableBeanFactory</span> <span class="kd">extends</span> <span class="nc">BeanDefinitionRegistry</span> <span class="o">{</span> <span class="kd">private</span> <span class="kd">final</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">BeanDefinition</span><span class="o">&gt;</span> <span class="n">beanDefinitionMap</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ConcurrentHashMap</span><span class="o">&lt;&gt;(</span><span class="mi">256</span><span class="o">);</span> <span class="kd">private</span> <span class="kd">volatile</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">beanDefinitionNames</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;(</span><span class="mi">256</span><span class="o">);</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <h3 id="3-实例化bean">3. 实例化Bean</h3> <p>这块逻辑的位置在 <code class="language-plaintext highlighter-rouge">org.springframework.beans.factory.support.DefaultListableBeanFactory::preInstantiateSingletons</code></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">class</span> <span class="nc">DefaultListableBeanFactory</span> <span class="kd">extends</span> <span class="nc">BeanDefinitionRegistry</span> <span class="o">{</span> <span class="kd">private</span> <span class="kd">volatile</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">beanDefinitionNames</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;(</span><span class="mi">256</span><span class="o">);</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">preInstantiateSingletons</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">BeansException</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">logger</span><span class="o">.</span><span class="na">isTraceEnabled</span><span class="o">())</span> <span class="o">{</span> <span class="n">logger</span><span class="o">.</span><span class="na">trace</span><span class="o">(</span><span class="s">"Pre-instantiating singletons in "</span> <span class="o">+</span> <span class="k">this</span><span class="o">);</span> <span class="o">}</span> <span class="c1">// Iterate over a copy to allow for init methods which in turn register new bean definitions.</span> <span class="c1">// While this may not be part of the regular factory bootstrap, it does otherwise work fine.</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">beanNames</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;(</span><span class="k">this</span><span class="o">.</span><span class="na">beanDefinitionNames</span><span class="o">);</span> <span class="c1">// Trigger initialization of all non-lazy singleton beans...</span> <span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="n">beanName</span> <span class="o">:</span> <span class="n">beanNames</span><span class="o">)</span> <span class="o">{</span> <span class="nc">RootBeanDefinition</span> <span class="n">bd</span> <span class="o">=</span> <span class="n">getMergedLocalBeanDefinition</span><span class="o">(</span><span class="n">beanName</span><span class="o">);</span> <span class="k">if</span> <span class="o">(!</span><span class="n">bd</span><span class="o">.</span><span class="na">isAbstract</span><span class="o">()</span> <span class="o">&amp;&amp;</span> <span class="n">bd</span><span class="o">.</span><span class="na">isSingleton</span><span class="o">()</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">bd</span><span class="o">.</span><span class="na">isLazyInit</span><span class="o">())</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="n">isFactoryBean</span><span class="o">(</span><span class="n">beanName</span><span class="o">))</span> <span class="o">{</span> <span class="nc">Object</span> <span class="n">bean</span> <span class="o">=</span> <span class="n">getBean</span><span class="o">(</span><span class="no">FACTORY_BEAN_PREFIX</span> <span class="o">+</span> <span class="n">beanName</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">bean</span> <span class="k">instanceof</span> <span class="nc">FactoryBean</span><span class="o">)</span> <span class="o">{</span> <span class="nc">FactoryBean</span><span class="o">&lt;?&gt;</span> <span class="n">factory</span> <span class="o">=</span> <span class="o">(</span><span class="nc">FactoryBean</span><span class="o">&lt;?&gt;)</span> <span class="n">bean</span><span class="o">;</span> <span class="kt">boolean</span> <span class="n">isEagerInit</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">getSecurityManager</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">&amp;&amp;</span> <span class="n">factory</span> <span class="k">instanceof</span> <span class="nc">SmartFactoryBean</span><span class="o">)</span> <span class="o">{</span> <span class="n">isEagerInit</span> <span class="o">=</span> <span class="nc">AccessController</span><span class="o">.</span><span class="na">doPrivileged</span><span class="o">(</span> <span class="o">(</span><span class="nc">PrivilegedAction</span><span class="o">&lt;</span><span class="nc">Boolean</span><span class="o">&gt;)</span> <span class="o">((</span><span class="nc">SmartFactoryBean</span><span class="o">&lt;?&gt;)</span> <span class="n">factory</span><span class="o">)::</span><span class="n">isEagerInit</span><span class="o">,</span> <span class="n">getAccessControlContext</span><span class="o">());</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="n">isEagerInit</span> <span class="o">=</span> <span class="o">(</span><span class="n">factory</span> <span class="k">instanceof</span> <span class="nc">SmartFactoryBean</span> <span class="o">&amp;&amp;</span> <span class="o">((</span><span class="nc">SmartFactoryBean</span><span class="o">&lt;?&gt;)</span> <span class="n">factory</span><span class="o">).</span><span class="na">isEagerInit</span><span class="o">());</span> <span class="o">}</span> <span class="k">if</span> <span class="o">(</span><span class="n">isEagerInit</span><span class="o">)</span> <span class="o">{</span> <span class="n">getBean</span><span class="o">(</span><span class="n">beanName</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="n">getBean</span><span class="o">(</span><span class="n">beanName</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> <span class="c1">// Trigger post-initialization callback for all applicable beans...</span> <span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="n">beanName</span> <span class="o">:</span> <span class="n">beanNames</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Object</span> <span class="n">singletonInstance</span> <span class="o">=</span> <span class="n">getSingleton</span><span class="o">(</span><span class="n">beanName</span><span class="o">);</span> <span class="k">if</span> <span class="o">(</span><span class="n">singletonInstance</span> <span class="k">instanceof</span> <span class="nc">SmartInitializingSingleton</span><span class="o">)</span> <span class="o">{</span> <span class="nc">StartupStep</span> <span class="n">smartInitialize</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">getApplicationStartup</span><span class="o">().</span><span class="na">start</span><span class="o">(</span><span class="s">"spring.beans.smart-initialize"</span><span class="o">)</span> <span class="o">.</span><span class="na">tag</span><span class="o">(</span><span class="s">"beanName"</span><span class="o">,</span> <span class="n">beanName</span><span class="o">);</span> <span class="nc">SmartInitializingSingleton</span> <span class="n">smartSingleton</span> <span class="o">=</span> <span class="o">(</span><span class="nc">SmartInitializingSingleton</span><span class="o">)</span> <span class="n">singletonInstance</span><span class="o">;</span> <span class="k">if</span> <span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">getSecurityManager</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="nc">AccessController</span><span class="o">.</span><span class="na">doPrivileged</span><span class="o">((</span><span class="nc">PrivilegedAction</span><span class="o">&lt;</span><span class="nc">Object</span><span class="o">&gt;)</span> <span class="o">()</span> <span class="o">-&gt;</span> <span class="o">{</span> <span class="n">smartSingleton</span><span class="o">.</span><span class="na">afterSingletonsInstantiated</span><span class="o">();</span> <span class="k">return</span> <span class="kc">null</span><span class="o">;</span> <span class="o">},</span> <span class="n">getAccessControlContext</span><span class="o">());</span> <span class="o">}</span> <span class="k">else</span> <span class="o">{</span> <span class="n">smartSingleton</span><span class="o">.</span><span class="na">afterSingletonsInstantiated</span><span class="o">();</span> <span class="o">}</span> <span class="n">smartInitialize</span><span class="o">.</span><span class="na">end</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div></div> <p>这块主要做的事情是:</p> <ol> <li> <p>遍历每一个 beanDefinition,判断是否抽象、单例、懒加载,并对符合条件的 beanDefinition 进行实例化,完成 Bean 的注入。</p> <p>每个符合条件 beanDefinition 都会被调用一次 getBean 接口,找不到就会根据 beanDefinition 创建 Bean</p> </li> <li> <p>为所有合适的 Bean 触发初始化后回调</p> </li> </ol> <h2 id="总结">总结</h2> <ul> <li>@ConditionalOnMissingBean 判断的时候,是按照注册BeanDefinition的顺序进行判断的,BeanDefinition的顺序是由类的加载顺序保证的。这个流程中所有Bean都还没有实例化。</li> <li>对于使用注解@ConditionalOnMissingBean、@ConditionalOnBean去对某个Bean的注入增加条件时,需要保证依赖的Bean是优先于当前类进行加载的。</li> <li>如何控制加载顺序? <ul> <li>自动装配类的加载顺序是晚于普通配置类的</li> <li>自动装配类之前的加载顺序可以使用@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder进行保证。</li> </ul> </li> </ul> <p><img src="https://duanmourena.github.io//img/develop/spring-bean-timeline.png" alt="spring-bean-timeline" /></p> <h1 id="常用注解的加载时机">常用注解的加载时机</h1> <ul> <li> <p>@Component</p> <p>普通组件类,步骤一 parse 中加载</p> </li> <li> <p>@Configuration</p> <p>功能和@Component一致,叫@Configuration只是为了起个别名,标志特定分类的组件,和@Controller、@Service一致</p> </li> <li> <p>@Import(xxx.class)</p> <ul> <li> <p>若导入的类没有实现 DeferredImportSelector 接口</p> <p>普通组件类,功能和@Component一致,唯一的区别就是导入的类可以不用加@Component去标注。</p> </li> <li> <p>若导入的类实现了 DeferredImportSelector 接口</p> <p>延迟加载,步骤二deferredImportSelectorHandler的时候加载,加载晚于普通组件类</p> <p>自动装配类就属于这种方式,但Spring给自动装配类增加了排序的特性,自动装配类之间可以排序。</p> </li> </ul> </li> <li> <p>@Enablexxx</p> <p>常见于通过@Enable开头的注解,用于某个功能的开启。</p> <p>这种实现本质上就是通过一个见名知意的注解去包装多个注解的功能,和把里面包装的注解写出来没区别,具体还是要看里面包装的注解做了什么事情</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 </pre></td><td class="rouge-code"><pre><span class="nd">@Import</span><span class="o">(</span><span class="nc">AutoConfigurationImportSelector</span><span class="o">.</span><span class="na">class</span><span class="o">)</span> <span class="kd">public</span> <span class="nd">@interface</span> <span class="nc">EnableAutoConfiguration</span> <span class="o">{</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div> </div> </li> <li> <p>@AutoConfiguration</p> <p>无任何实质性的作用,只是把@Configuration、@AutoConfigureBefore、@AutoConfigureAfter这三个注解包装成一个注解,标识这是一个自动装配类,方便自动装配类之间的排序。</p> <p>但和上述@Enablexxx有个区别,Spring认这个注解,Spring在步骤一parse中对@ComponentScan的扫描阶段,发现此类如果用@AutoConfiguration标注,就会认为你是一个自动装配类,不进行扫描,跳过当前类</p> </li> <li> <p>@AutoConfigureBefore、@AutoConfigureAfter、@AutoConfigureOrder</p> <p>只生效于自动装配类,用于自动装配类之间的排序</p> </li> </ul> <h1 id="自动装配类的配置">自动装配类的配置</h1> <p>自动装配类有两种配置方式</p> <ul> <li> <p>resources/spring.factories</p> <p>key为EnableAutoConfiguration的全限定类名</p> <p>value为自动装配类的全限定类名</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 </pre></td><td class="rouge-code"><pre>org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.example.c.config.CConfig </pre></td></tr></tbody></table></code></pre></div> </div> </li> <li> <p>resources/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports</p> <p>里面一行一行的填自动装配类的全限定类名</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 </pre></td><td class="rouge-code"><pre>com.example.c.config.CConfig </pre></td></tr></tbody></table></code></pre></div> </div> <p>源码解析位置</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">class</span> <span class="nc">AutoConfigurationImportSelector</span> <span class="kd">implements</span> <span class="nc">DeferredImportSelector</span><span class="o">{</span> <span class="kd">protected</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="nf">getCandidateConfigurations</span><span class="o">(</span><span class="nc">AnnotationMetadata</span> <span class="n">metadata</span><span class="o">,</span> <span class="nc">AnnotationAttributes</span> <span class="n">attributes</span><span class="o">)</span> <span class="o">{</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">configurations</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;(</span> <span class="nc">SpringFactoriesLoader</span><span class="o">.</span><span class="na">loadFactoryNames</span><span class="o">(</span><span class="n">getSpringFactoriesLoaderFactoryClass</span><span class="o">(),</span> <span class="n">getBeanClassLoader</span><span class="o">()));</span> <span class="nc">ImportCandidates</span><span class="o">.</span><span class="na">load</span><span class="o">(</span><span class="nc">AutoConfiguration</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="n">getBeanClassLoader</span><span class="o">()).</span><span class="na">forEach</span><span class="o">(</span><span class="nl">configurations:</span><span class="o">:</span><span class="n">add</span><span class="o">);</span> <span class="nc">Assert</span><span class="o">.</span><span class="na">notEmpty</span><span class="o">(</span><span class="n">configurations</span><span class="o">,</span> <span class="s">"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "</span> <span class="o">+</span> <span class="s">"are using a custom packaging, make sure that file is correct."</span><span class="o">);</span> <span class="k">return</span> <span class="n">configurations</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div> </div> </li> </ul> <h1 id="常见问题">常见问题</h1> <ol> <li> <p>有一个类,既用@Configuration标注,又配置成了自动装配类,那他的解析时机发生在哪?</p> <p>Spring在步骤一parse中对@ComponentScan的扫描阶段,会经过一系列的过滤器,其中一个是自动配置类的过滤器</p> <p>这个过滤器的判断条件是,如果当前类用@Configuration标注,并且修饰的注解有@AutoConfiguration,或者配置成了自动装配类,那就跳过当前类的扫描</p> <p>因此,这种场景下,此类扫描的时机应是走自动装配类扫描的逻辑。</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 </pre></td><td class="rouge-code"><pre><span class="kd">public</span> <span class="kd">class</span> <span class="nc">AutoConfigurationExcludeFilter</span> <span class="kd">implements</span> <span class="nc">TypeFilter</span><span class="o">,</span> <span class="nc">BeanClassLoaderAware</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">match</span><span class="o">(</span><span class="nc">MetadataReader</span> <span class="n">metadataReader</span><span class="o">,</span> <span class="nc">MetadataReaderFactory</span> <span class="n">metadataReaderFactory</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span> <span class="k">return</span> <span class="nf">isConfiguration</span><span class="o">(</span><span class="n">metadataReader</span><span class="o">)</span> <span class="o">&amp;&amp;</span> <span class="n">isAutoConfiguration</span><span class="o">(</span><span class="n">metadataReader</span><span class="o">);</span> <span class="o">}</span> <span class="kd">private</span> <span class="kt">boolean</span> <span class="nf">isConfiguration</span><span class="o">(</span><span class="nc">MetadataReader</span> <span class="n">metadataReader</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">metadataReader</span><span class="o">.</span><span class="na">getAnnotationMetadata</span><span class="o">().</span><span class="na">isAnnotated</span><span class="o">(</span><span class="nc">Configuration</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span> <span class="o">}</span> <span class="kd">private</span> <span class="kt">boolean</span> <span class="nf">isAutoConfiguration</span><span class="o">(</span><span class="nc">MetadataReader</span> <span class="n">metadataReader</span><span class="o">)</span> <span class="o">{</span> <span class="kt">boolean</span> <span class="n">annotatedWithAutoConfiguration</span> <span class="o">=</span> <span class="n">metadataReader</span><span class="o">.</span><span class="na">getAnnotationMetadata</span><span class="o">()</span> <span class="o">.</span><span class="na">isAnnotated</span><span class="o">(</span><span class="nc">AutoConfiguration</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getName</span><span class="o">());</span> <span class="k">return</span> <span class="n">annotatedWithAutoConfiguration</span> <span class="o">||</span> <span class="n">getAutoConfigurations</span><span class="o">().</span><span class="na">contains</span><span class="o">(</span><span class="n">metadataReader</span><span class="o">.</span><span class="na">getClassMetadata</span><span class="o">().</span><span class="na">getClassName</span><span class="o">());</span> <span class="o">}</span> <span class="kd">protected</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="nf">getAutoConfigurations</span><span class="o">()</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">autoConfigurations</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">&gt;</span> <span class="n">autoConfigurations</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;(</span> <span class="nc">SpringFactoriesLoader</span><span class="o">.</span><span class="na">loadFactoryNames</span><span class="o">(</span><span class="nc">EnableAutoConfiguration</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">beanClassLoader</span><span class="o">));</span> <span class="nc">ImportCandidates</span><span class="o">.</span><span class="na">load</span><span class="o">(</span><span class="nc">AutoConfiguration</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="k">this</span><span class="o">.</span><span class="na">beanClassLoader</span><span class="o">).</span><span class="na">forEach</span><span class="o">(</span><span class="nl">autoConfigurations:</span><span class="o">:</span><span class="n">add</span><span class="o">);</span> <span class="k">this</span><span class="o">.</span><span class="na">autoConfigurations</span> <span class="o">=</span> <span class="n">autoConfigurations</span><span class="o">;</span> <span class="o">}</span> <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">autoConfigurations</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> </pre></td></tr></tbody></table></code></pre></div> </div> </li> <li> <p>如果一个类用@AutoConfiguration注解修饰,但没有通过file文件的方式配置为自动装配类,那此类是什么时机被扫描的</p> <p>和上述逻辑一致,检测到使用@AutoConfiguration修饰,则在步骤一parse中会跳过当前类的扫描。</p> <p>但在步骤二中,又在自动装配类中没有找到当前类,则当前类不会被扫描。</p> </li> </ol>

2023/10/14
阅读更多

Hello 2023

<blockquote> <p>“Yeah It’s on. ”</p> </blockquote> <p>2023 08 17 晚 23:30 有了一盏灯,希望可以照亮那个远方…</p>

2023/8/17
阅读更多