答讀者問:關於隱式 id 重複的問題

張哥說技術發表於2023-11-16


來源:江南一點雨


我自己天天跟大夥講 Spring 原始碼,我基本都是分析原始碼來講。小夥伴們學習了之後,經常會產生許多千奇百怪的想法,這些想法都很不錯,往往這些想法還給了我很大的啟發,讓我發現原來這個問題還可以從這個角度來理解。

今天我們來看一個小夥伴的提問:

答讀者問:關於隱式 id 重複的問題答讀者問:關於隱式 id 重複的問題

首先我得先誇一句,這個問題真的非常好!問題非常詳細,有原始碼有截圖有版本號,該說的都說了,問題非常清晰,我一看就知道發生了什麼事情,每天在微信上問松哥問題的人不少,能把問題說的這麼清楚的人屈指可數。

我跟大家講一下這個問題的上下文:

Spring 中 beanName 是不能重複的,一般情況下,我們在定義 Bean 的時候,都要為其指定 beanName 屬性,如果不指定,則會預設生成 beanName。在 XML 配置中,如果我們不指定 beanName 或者 id,那麼預設生成的 beanName 就是類名的完整路徑或者是 類名完整路徑+#+編號。這個小夥伴就是在學習了上述內容之後,提出來這個問題。

關於 beanName 自動生成邏輯松哥在影片中都已經詳細介紹過了,因此這裡就簡單和大家梳理一下思路,具體可以參考 Spring 原始碼影片。

問題分析

小夥伴一共提出兩個問題,我們分別來看。

問題一

首先定義了一個 User 物件,但是並未指定 beanName,按照松哥之前在 Spring 原始碼影片中所講的,此時會自動給這個 bean 生成 id 和別名,別名是類名的完整路徑,即 cn.junhaox.entity.User,id 則是類名完整路徑+編號,即 cn.junhaox.entity.User#0,即我們可以透過這兩個任意一個名稱來訪問到第一個物件。

第二個 bean 在定義的時候,則指定了 id,而且指定的 id 恰好就是第一個 bean 自動生成的 id。

這個邏輯上顯然是衝突了,導致最終訪問的時候,透過 cn.junhaox.entity.User#0 或者 cn.junhaox.entity.User 訪問到的是第二個 bean 而不是第一個 bean,這就給人一種第一個 bean 似乎註冊失敗了的感覺。

我們先來分析一下這個問題。

先來說 bean 的註冊,當 bean 在註冊的時候,首先會去檢查當前 beanName 是否重複(具體在 org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseBeanDefinitionElement(org.w3c.dom.Element, org.springframework.beans.factory.config.BeanDefinition) 方法中),但是這個檢查主要是檢查我們自己手動配置的 beanName 是否存在重複的情況,並不會去檢查自動生成的 beanName 是否重複,這就導致了當第二個 bean 在註冊的時候,檢查 beanName 是否重複的時候,結果發現 beanName 並不重複,因此就導致了 cn.junhaox.entity.User#0 beanName 重新指向了第二個 bean,那毫無疑問,cn.junhaox.entity.User 作為別名,也重新指向了第二個 bean。

這就是第一個問題產生的原因。

問題二

根據前面的分析,小夥伴們已經知道,對於第一個 bean,由於即沒有配置 id,又沒有配置 beanName,所以第一個 bean 在註冊的時候,會自動生成 id cn.junhaox.entity.User#0 並且會自動生成 beanName cn.junhaox.entity.User

現在第二個問題就是把第一個 bean 的別名作為第二個 bean 的 id 了,導致第二個 bean 似乎訪問不到了。

松哥先來說結論,這個問題其實目前在最新版的 Spring 中已經不存在了,具體的處理是在 2022 年 2 月 5 號提交的程式碼中解決了問題,在當年 3 月份釋出的 v6.0.0-M3 版本中這塊的程式碼改過來了,我們來看下程式碼的變化大家就明白了:

答讀者問:關於隱式 id 重複的問題

大家可以看到,變化發生在 DefaultListableBeanFactory#registerBeanDefinition 方法中,綠色的程式碼塊就是新增的程式碼。

新增的程式碼主要是當我們向容器註冊一個 BeanDefinition 的時候,首先會去檢查這個 beanName 是否是一個別名,如果是,則檢查別名是否允許覆蓋,如果別名不允許覆蓋,那麼該拋異常就拋異常,如果別名允許覆蓋,則呼叫 removeAlias 方法移除別名,這個移除相當於剪掉了別名之間的關係,cn.junhaox.entity.User 將不再作為別名指向 cn.junhaox.entity.User#0 了。

因此,對於第二個問題,從 Spring6.0.0-M3 開始,透過 cn.junhaox.entity.User#0 可以訪問到第一個 bean,透過 cn.junhaox.entity.User 則可以訪問到第二個 bean。

但是,在此版本之前,並未檢查當前 beanName 是否是一個別名,而是直接使用該 beanName 進行註冊。當我們去查詢 bean 的時候,都是根據 beanName 去查詢 bean 的,如果是根據型別,最終也會先根據型別找出 beanName,然後再去查詢 bean。根據 beanName 去搜尋 bean 的時候,會先根據別名鏈條確定出最終的 beanName,由於 cn.junhaox.entity.Usercn.junhaox.entity.User#0 之間還存在別名關係,因此當我們按照 beanName cn.junhaox.entity.User 去搜尋 bean 的時候,系統會找到這是 cn.junhaox.entity.User#0 的別名,進而找出來 cn.junhaox.entity.User#0 所對應的 bean 並返回,這就導致第二個 bean 將來無法被查詢到。

好啦,現在這兩個問題都搞明白了吧~

以上內容松哥主要是和大家分享思路,技術細節包括涉及到的 Spring 原始碼細節在之前的 Spring 影片中都講過,大家可以參考影片。

歡迎大家繼續提問!



來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024923/viewspace-2995591/,如需轉載,請註明出處,否則將追究法律責任。

相關文章