簡單聊一聊Spring中Bean別名的處理原理
1. Alias
別名,顧名思義就是給一個 Bean 去兩個甚至多個名字。整體上來說,在 Spring 中,有兩種不同的別名定義方式:
- 定義 Bean 的 name 屬性,name 屬性在真正的處理過程中,實際上就是按照別名來處理的。
- 通過 alias 標簽定義專門的別名,通過 alias 定義出來的別名和 name 屬性定義的別名最終都是合并在一起處理的,所以這兩種定義別名的方式最終是殊途同歸。
那么定義的別名是保存在哪里呢?
大家知道,Bean 解析出來之后被保存在容器中,別名其實也是一樣的,容器中存在一個 aliasMap 專門用來保存 Bean 的別名,保存的格式是 alias->name,例如有一個 Bean 的名字是 user,別名是 userAlias,那么保存在 aliasMap 中就是 userAlias->user。
舉個簡單例子:
<bean class="org.javaboy.demo.User" id="user" name="user4,user5,user6"/> <alias name="user" alias="user2"/> <alias name="user2" alias="user3"/>
在上面這段定義中,user2、user3、user4、user5、user6 都是別名。
2. AliasRegistry
2.1 AliasRegistry
Spring 中為別名的處理提供了 AliasRegistry 接口,這個接口中提供了別名處理的主要方法:
public interface AliasRegistry { void registerAlias(String name, String alias); void removeAlias(String alias); boolean isAlias(String name); String[] getAliases(String name); }
- registerAlias:這個方法用來添加別名,核心邏輯就是向 aliasMap 中添加數(shù)據(jù)。
- removeAlias:這個方法用來從 aliasMap 中移除一個別名。
- isAlias:判斷給定的 name 是否是一個別名。
- getAliases:根據(jù)給定的名字去獲取所有的別名。
方法就這四個,看一下這個接口的實現(xiàn)類有哪些。
大家看到,AliasRegistry 的實現(xiàn)類其實還是蠻多的,但是大部分都是容器,真正實現(xiàn)了 AliasRegistry 中四個方法的只有 SimpleAliasRegistry,其他的容器大部分其實都是為了具備別名管理的能力,繼承了 SimpleAliasRegistry。
所以真正給我們整活的其實是 SimpleAliasRegistry。
2.2 SimpleAliasRegistry
SimpleAliasRegistry 類中的內(nèi)容比較多,為了講解方便,我就挨個貼屬性和方法出來,貼出來后和大家分享。
private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);
首先,SimpleAliasRegistry 中定義了一個 aliasMap,這個就是用來保存別名的,這是一個 Map 集合,接下來所有的操作都是圍繞這個集合展開。
@Override public void removeAlias(String alias) { synchronized (this.aliasMap) { String name = this.aliasMap.remove(alias); if (name == null) { throw new IllegalStateException("No alias '" + alias + "' registered"); } } }
這個方法用來移除別名,移除的思路很簡單,就是從 aliasMap 中移除數(shù)據(jù)即可,如果 remove 方法返回值為 null 那就說明要移除的別名不存在,那么直接拋出異常。
@Override public boolean isAlias(String name) { return this.aliasMap.containsKey(name); }
這個是判斷是否包含某一個別名,這個判斷簡單。有一個跟它容易產(chǎn)生歧義的方法,如下:
public boolean hasAlias(String name, String alias) { String registeredName = this.aliasMap.get(alias); return ObjectUtils.nullSafeEquals(registeredName, name) || (registeredName != null && hasAlias(name, registeredName)); }
這個方法是判斷給定的 name 和 alias 之間是否具備關(guān)聯(lián)關(guān)系。判斷的邏輯就是先去 aliasMap 中,根據(jù) alias 查出來這個 alias 所對應的真實 beanName,即 registeredName,然后判斷 registeredName 和 name 是否相等,如果相等就直接返回,如果不相等就繼續(xù)遞歸調(diào)用,為什么要遞歸呢?因為 aliasMap 中存在的別名可能是這樣的:
- a->b
- b->c
- c->d
即 a 是 b 的別名,b 是 c 的別名,c 是 d 的別名,現(xiàn)在如果想要判斷 a 和 d 之間的關(guān)系,那么根據(jù) a 查出來的 b 顯然不等于 d,所以要繼續(xù)遞歸,再根據(jù) b 查 c,根據(jù) c 查到 d,這樣就能確定 a 和 d 是否有關(guān)系了。
@Override public String[] getAliases(String name) { List<String> result = new ArrayList<>(); synchronized (this.aliasMap) { retrieveAliases(name, result); } return StringUtils.toStringArray(result); } private void retrieveAliases(String name, List<String> result) { this.aliasMap.forEach((alias, registeredName) -> { if (registeredName.equals(name)) { result.add(alias); retrieveAliases(alias, result); } }); }
getAliases 方法是根據(jù)傳入的 name 找到其對應的別名,但是由于別名可能存在多個,所以調(diào)用 retrieveAliases 方法遞歸去查找所有的別名,將找到的別名都存入到一個集合中,最終將集合轉(zhuǎn)為數(shù)組返回。
protected void checkForAliasCircle(String name, String alias) { if (hasAlias(alias, name)) { throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" + name + "': Circular reference - '" + name + "' is a direct or indirect alias for '" + alias + "' already"); } }
這個方法用來檢查別名是否存在死結(jié),即 a 是 b 的別名,b 是 a 的別名這種情況。檢查的方式很簡單,就是調(diào)用 hasAlias 方法,但是將傳入的兩個參數(shù)顛倒過來就可以了。
public void resolveAliases(StringValueResolver valueResolver) { synchronized (this.aliasMap) { Map<String, String> aliasCopy = new HashMap<>(this.aliasMap); aliasCopy.forEach((alias, registeredName) -> { String resolvedAlias = valueResolver.resolveStringValue(alias); String resolvedName = valueResolver.resolveStringValue(registeredName); if (resolvedAlias == null || resolvedName == null || resolvedAlias.equals(resolvedName)) { this.aliasMap.remove(alias); } else if (!resolvedAlias.equals(alias)) { String existingName = this.aliasMap.get(resolvedAlias); if (existingName != null) { if (existingName.equals(resolvedName)) { this.aliasMap.remove(alias); return; } throw new IllegalStateException( "Cannot register resolved alias '" + resolvedAlias + "' (original: '" + alias + "') for name '" + resolvedName + "': It is already registered for name '" + registeredName + "'."); } checkForAliasCircle(resolvedName, resolvedAlias); this.aliasMap.remove(alias); this.aliasMap.put(resolvedAlias, resolvedName); } else if (!registeredName.equals(resolvedName)) { this.aliasMap.put(alias, resolvedName); } }); } }
這個方法是處理別名是占位符的情況,例如當引入了一個 .properties
文件之后,那么在配置別名的時候就可以引用 .properties
中的變量,那么上面這個方法就是用來解析變量的。
例如下面這種情況,我有一個 alias.properties,如下:
name=user alias=javaboy
然后在 XML 文件中使用這個 properties 文件,如下:
<context:property-placeholder location="classpath:alias.properties"/> <alias name="${name}" alias="${alias}"/>
對于這種情況,一開始存入到 aliasMap 中的就是占位符了,resolveAliases 方法就是要將這些占位符解析為具體的字符串。
大家看到,首先這里將 aliasMap 復制一份,生成一個 aliasCopy,然后進行遍歷。在遍歷時,根據(jù) valueResolver 將引用使用的占位符解析為真正的字符,如果解析出來的。如果解析出來的 name 和別名是相同的,那么顯然是有問題的,就需要把這個別名移除掉。
繼續(xù)判斷,如果解析出來的別名和原本的別名不相等(說明別名使用了占位符),那么就去檢查一下這個別名對應的 name,如果這個 name 已經(jīng)存在,且等于占位符解析出來的 name,說明這個別名已經(jīng)被定義過了,即重復定義,那么就把別名移除掉即可。如果這個別名指向的 name 和占位符解析出來的 name 不相等,說明試圖讓一個別名指向兩個 bean,那么就直接拋出異常了。
如果解析出來的別名還沒有指向 name 屬性的話,那么就正常處理,檢查是否存在死結(jié)、移除帶占位符的別名,存入解析之后的別名。
最后,如果原本的名稱和解析之后的屬性名稱不相等,那么就直接保存這個別名即可。
@Override public void registerAlias(String name, String alias) { synchronized (this.aliasMap) { if (alias.equals(name)) { this.aliasMap.remove(alias); } else { String registeredName = this.aliasMap.get(alias); if (registeredName != null) { if (registeredName.equals(name)) { return; } if (!allowAliasOverriding()) { throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'."); } } checkForAliasCircle(name, alias); this.aliasMap.put(alias, name); } } }
這個就是使用最多的別名注冊了,傳入的參數(shù)分別是 bean 的 name 和 alias,如果 alias 跟 name 相等,二話不說直接移除,這個 alias 有問題。
否則就去查詢這個 alias,檢查這個 alias 是否已經(jīng)有對應的 name 了,如果有,且等于傳入的 name,那么直接返回就行了,不用注冊,因為已經(jīng)注冊過了;如果有且不等于傳入的 name,那么就拋出異常,因為一個 alias 不能指向兩個 name。最后就是檢查和保存了。
public String canonicalName(String name) { String canonicalName = name; String resolvedName; do { resolvedName = this.aliasMap.get(canonicalName); if (resolvedName != null) { canonicalName = resolvedName; } } while (resolvedName != null); return canonicalName; }
這個方法用來解析出來別名里邊頂格的名字,例如有一個 bean 有很多別名,a->b,b->c,c->d,那么這個方法的目的就是傳入 a、b、c 中任意一個,返回 d 即可。因為 Spring 容器在處理的時候,并不用管這么多別名問題,容器只需要專注一個名字即可,因為最后一個別名實際上就是指向真實的 beanId 了,所以最終拿到的 bean 名稱其實相當于 bean 的 ID 了。
別名的處理主要就是這些方法。
3. 原理分析
前面我們說了,別名的來源主要是兩個地方:name 屬性和 alias 標簽,我們分別來看。
3.1 name 處理
對于 name 屬性的處理,有兩個地方,一個是在 bean 定義解析的時候,將 name 屬性解析為 alias,具體在 BeanDefinitionParserDelegate#parseBeanDefinitionElement 方法中(這個方法在之前跟大家講 bean 的默認名稱生成策略的時候,見過):
@Nullable public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) { String id = ele.getAttribute(ID_ATTRIBUTE); String nameAttr = ele.getAttribute(NAME_ATTRIBUTE); List<String> aliases = new ArrayList<>(); if (StringUtils.hasLength(nameAttr)) { String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS); aliases.addAll(Arrays.asList(nameArr)); } //省略其他 }
可以看到,這里就從 XML 節(jié)點中提取出來 name 屬性,然后切分為一個數(shù)組,并將之存入到 aliases 屬性中。接下來在后續(xù)的 BeanDefinitionReaderUtils#registerBeanDefinition 方法中,再把 aliases 中的值注冊一下,如下:
public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { String beanName = definitionHolder.getBeanName(); registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } } }
這就是 XML 中的 name 屬性是如何變?yōu)閯e名的。
3.2 別名標簽處理
別名的另一個來源是別名標簽,在 Spring 解析 XML 標簽的時候,有針對別名標簽的專門處理,具體位置是在 DefaultBeanDefinitionDocumentReader#parseDefaultElement 方法中:
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { importBeanDefinitionResource(ele); } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { processAliasRegistration(ele); } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // recurse doRegisterBeanDefinitions(ele); } }
這里會去判斷標簽的類型,如果是別名,就調(diào)用 processAliasRegistration 方法進行處理:
protected void processAliasRegistration(Element ele) { String name = ele.getAttribute(NAME_ATTRIBUTE); String alias = ele.getAttribute(ALIAS_ATTRIBUTE); boolean valid = true; if (!StringUtils.hasText(name)) { getReaderContext().error("Name must not be empty", ele); valid = false; } if (!StringUtils.hasText(alias)) { getReaderContext().error("Alias must not be empty", ele); valid = false; } if (valid) { try { getReaderContext().getRegistry().registerAlias(name, alias); } catch (Exception ex) { getReaderContext().error("Failed to register alias '" + alias + "' for bean with name '" + name + "'", ele, ex); } getReaderContext().fireAliasRegistered(name, alias, extractSource(ele)); } }
可以看到,這里也是從 XML 文件中的別名標簽上,提取出來 name 和 alias 屬性值,最后調(diào)用 registerAlias 方法進行注冊。
以上就是簡單聊一聊Spring中Bean別名的處理原理的詳細內(nèi)容,更多關(guān)于Spring Bean別名處理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于重寫equals()方法和hashCode()方法及其簡單的應用
這篇文章主要介紹了關(guān)于重寫equals()方法和hashCode()方法及其簡單的應用,網(wǎng)上的知識有些可能是錯誤的,關(guān)于?equals()?方法的理解,大家討論不一樣,需要的朋友可以參考下2023-04-04地址到經(jīng)緯度坐標轉(zhuǎn)化的JAVA代碼
這篇文章介紹了地址到經(jīng)緯度坐標轉(zhuǎn)化的JAVA代碼,有需要的朋友可以參考一下2013-09-09