簡(jiǎn)單聊一聊Spring中Bean別名的處理原理
1. Alias
別名,顧名思義就是給一個(gè) Bean 去兩個(gè)甚至多個(gè)名字。整體上來(lái)說(shuō),在 Spring 中,有兩種不同的別名定義方式:
- 定義 Bean 的 name 屬性,name 屬性在真正的處理過(guò)程中,實(shí)際上就是按照別名來(lái)處理的。
- 通過(guò) alias 標(biāo)簽定義專(zhuān)門(mén)的別名,通過(guò) alias 定義出來(lái)的別名和 name 屬性定義的別名最終都是合并在一起處理的,所以這兩種定義別名的方式最終是殊途同歸。
那么定義的別名是保存在哪里呢?
大家知道,Bean 解析出來(lái)之后被保存在容器中,別名其實(shí)也是一樣的,容器中存在一個(gè) aliasMap 專(zhuān)門(mén)用來(lái)保存 Bean 的別名,保存的格式是 alias->name,例如有一個(gè) Bean 的名字是 user,別名是 userAlias,那么保存在 aliasMap 中就是 userAlias->user。
舉個(gè)簡(jiǎn)單例子:
<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 接口,這個(gè)接口中提供了別名處理的主要方法:
public interface AliasRegistry {
void registerAlias(String name, String alias);
void removeAlias(String alias);
boolean isAlias(String name);
String[] getAliases(String name);
}- registerAlias:這個(gè)方法用來(lái)添加別名,核心邏輯就是向 aliasMap 中添加數(shù)據(jù)。
- removeAlias:這個(gè)方法用來(lái)從 aliasMap 中移除一個(gè)別名。
- isAlias:判斷給定的 name 是否是一個(gè)別名。
- getAliases:根據(jù)給定的名字去獲取所有的別名。
方法就這四個(gè),看一下這個(gè)接口的實(shí)現(xiàn)類(lèi)有哪些。

大家看到,AliasRegistry 的實(shí)現(xiàn)類(lèi)其實(shí)還是蠻多的,但是大部分都是容器,真正實(shí)現(xiàn)了 AliasRegistry 中四個(gè)方法的只有 SimpleAliasRegistry,其他的容器大部分其實(shí)都是為了具備別名管理的能力,繼承了 SimpleAliasRegistry。
所以真正給我們整活的其實(shí)是 SimpleAliasRegistry。
2.2 SimpleAliasRegistry
SimpleAliasRegistry 類(lèi)中的內(nèi)容比較多,為了講解方便,我就挨個(gè)貼屬性和方法出來(lái),貼出來(lái)后和大家分享。
private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);
首先,SimpleAliasRegistry 中定義了一個(gè) aliasMap,這個(gè)就是用來(lái)保存別名的,這是一個(gè) Map 集合,接下來(lái)所有的操作都是圍繞這個(gè)集合展開(kāi)。
@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");
}
}
}這個(gè)方法用來(lái)移除別名,移除的思路很簡(jiǎn)單,就是從 aliasMap 中移除數(shù)據(jù)即可,如果 remove 方法返回值為 null 那就說(shuō)明要移除的別名不存在,那么直接拋出異常。
@Override
public boolean isAlias(String name) {
return this.aliasMap.containsKey(name);
}這個(gè)是判斷是否包含某一個(gè)別名,這個(gè)判斷簡(jiǎn)單。有一個(gè)跟它容易產(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));
}這個(gè)方法是判斷給定的 name 和 alias 之間是否具備關(guān)聯(lián)關(guān)系。判斷的邏輯就是先去 aliasMap 中,根據(jù) alias 查出來(lái)這個(gè) alias 所對(duì)應(yīng)的真實(shí) beanName,即 registeredName,然后判斷 registeredName 和 name 是否相等,如果相等就直接返回,如果不相等就繼續(xù)遞歸調(diào)用,為什么要遞歸呢?因?yàn)?aliasMap 中存在的別名可能是這樣的:
- a->b
- b->c
- c->d
即 a 是 b 的別名,b 是 c 的別名,c 是 d 的別名,現(xiàn)在如果想要判斷 a 和 d 之間的關(guān)系,那么根據(jù) a 查出來(lái)的 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 找到其對(duì)應(yīng)的別名,但是由于別名可能存在多個(gè),所以調(diào)用 retrieveAliases 方法遞歸去查找所有的別名,將找到的別名都存入到一個(gè)集合中,最終將集合轉(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");
}
}這個(gè)方法用來(lái)檢查別名是否存在死結(jié),即 a 是 b 的別名,b 是 a 的別名這種情況。檢查的方式很簡(jiǎn)單,就是調(diào)用 hasAlias 方法,但是將傳入的兩個(gè)參數(shù)顛倒過(guò)來(lái)就可以了。
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);
}
});
}
}這個(gè)方法是處理別名是占位符的情況,例如當(dāng)引入了一個(gè) .properties 文件之后,那么在配置別名的時(shí)候就可以引用 .properties 中的變量,那么上面這個(gè)方法就是用來(lái)解析變量的。
例如下面這種情況,我有一個(gè) alias.properties,如下:
name=user alias=javaboy
然后在 XML 文件中使用這個(gè) properties 文件,如下:
<context:property-placeholder location="classpath:alias.properties"/>
<alias name="${name}" alias="${alias}"/>對(duì)于這種情況,一開(kāi)始存入到 aliasMap 中的就是占位符了,resolveAliases 方法就是要將這些占位符解析為具體的字符串。
大家看到,首先這里將 aliasMap 復(fù)制一份,生成一個(gè) aliasCopy,然后進(jìn)行遍歷。在遍歷時(shí),根據(jù) valueResolver 將引用使用的占位符解析為真正的字符,如果解析出來(lái)的。如果解析出來(lái)的 name 和別名是相同的,那么顯然是有問(wèn)題的,就需要把這個(gè)別名移除掉。
繼續(xù)判斷,如果解析出來(lái)的別名和原本的別名不相等(說(shuō)明別名使用了占位符),那么就去檢查一下這個(gè)別名對(duì)應(yīng)的 name,如果這個(gè) name 已經(jīng)存在,且等于占位符解析出來(lái)的 name,說(shuō)明這個(gè)別名已經(jīng)被定義過(guò)了,即重復(fù)定義,那么就把別名移除掉即可。如果這個(gè)別名指向的 name 和占位符解析出來(lái)的 name 不相等,說(shuō)明試圖讓一個(gè)別名指向兩個(gè) bean,那么就直接拋出異常了。
如果解析出來(lái)的別名還沒(méi)有指向 name 屬性的話(huà),那么就正常處理,檢查是否存在死結(jié)、移除帶占位符的別名,存入解析之后的別名。
最后,如果原本的名稱(chēng)和解析之后的屬性名稱(chēng)不相等,那么就直接保存這個(gè)別名即可。
@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);
}
}
}這個(gè)就是使用最多的別名注冊(cè)了,傳入的參數(shù)分別是 bean 的 name 和 alias,如果 alias 跟 name 相等,二話(huà)不說(shuō)直接移除,這個(gè) alias 有問(wèn)題。
否則就去查詢(xún)這個(gè) alias,檢查這個(gè) alias 是否已經(jīng)有對(duì)應(yīng)的 name 了,如果有,且等于傳入的 name,那么直接返回就行了,不用注冊(cè),因?yàn)橐呀?jīng)注冊(cè)過(guò)了;如果有且不等于傳入的 name,那么就拋出異常,因?yàn)橐粋€(gè) alias 不能指向兩個(gè) 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;
}這個(gè)方法用來(lái)解析出來(lái)別名里邊頂格的名字,例如有一個(gè) bean 有很多別名,a->b,b->c,c->d,那么這個(gè)方法的目的就是傳入 a、b、c 中任意一個(gè),返回 d 即可。因?yàn)?Spring 容器在處理的時(shí)候,并不用管這么多別名問(wèn)題,容器只需要專(zhuān)注一個(gè)名字即可,因?yàn)樽詈笠粋€(gè)別名實(shí)際上就是指向真實(shí)的 beanId 了,所以最終拿到的 bean 名稱(chēng)其實(shí)相當(dāng)于 bean 的 ID 了。
別名的處理主要就是這些方法。
3. 原理分析
前面我們說(shuō)了,別名的來(lái)源主要是兩個(gè)地方:name 屬性和 alias 標(biāo)簽,我們分別來(lái)看。
3.1 name 處理
對(duì)于 name 屬性的處理,有兩個(gè)地方,一個(gè)是在 bean 定義解析的時(shí)候,將 name 屬性解析為 alias,具體在 BeanDefinitionParserDelegate#parseBeanDefinitionElement 方法中(這個(gè)方法在之前跟大家講 bean 的默認(rèn)名稱(chēng)生成策略的時(shí)候,見(jiàn)過(guò)):
@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é)點(diǎn)中提取出來(lái) name 屬性,然后切分為一個(gè)數(shù)組,并將之存入到 aliases 屬性中。接下來(lái)在后續(xù)的 BeanDefinitionReaderUtils#registerBeanDefinition 方法中,再把 aliases 中的值注冊(cè)一下,如下:
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 別名標(biāo)簽處理
別名的另一個(gè)來(lái)源是別名標(biāo)簽,在 Spring 解析 XML 標(biāo)簽的時(shí)候,有針對(duì)別名標(biāo)簽的專(zhuān)門(mén)處理,具體位置是在 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);
}
}這里會(huì)去判斷標(biāo)簽的類(lèi)型,如果是別名,就調(diào)用 processAliasRegistration 方法進(jìn)行處理:
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 文件中的別名標(biāo)簽上,提取出來(lái) name 和 alias 屬性值,最后調(diào)用 registerAlias 方法進(jìn)行注冊(cè)。
以上就是簡(jiǎn)單聊一聊Spring中Bean別名的處理原理的詳細(xì)內(nèi)容,更多關(guān)于Spring Bean別名處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于重寫(xiě)equals()方法和hashCode()方法及其簡(jiǎn)單的應(yīng)用
這篇文章主要介紹了關(guān)于重寫(xiě)equals()方法和hashCode()方法及其簡(jiǎn)單的應(yīng)用,網(wǎng)上的知識(shí)有些可能是錯(cuò)誤的,關(guān)于?equals()?方法的理解,大家討論不一樣,需要的朋友可以參考下2023-04-04
SpringBoot進(jìn)行多表查詢(xún)功能的實(shí)現(xiàn)
這篇文章主要介紹了SpringBoot進(jìn)行多表查詢(xún)功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09
java8新特性-lambda表達(dá)式入門(mén)學(xué)習(xí)心得
這篇文章主要介紹了java8新特性-lambda表達(dá)式入門(mén)學(xué)習(xí)心得,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
地址到經(jīng)緯度坐標(biāo)轉(zhuǎn)化的JAVA代碼
這篇文章介紹了地址到經(jīng)緯度坐標(biāo)轉(zhuǎn)化的JAVA代碼,有需要的朋友可以參考一下2013-09-09

