欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

簡單聊一聊Spring中Bean別名的處理原理

 更新時間:2023年09月05日 11:24:11   作者:江南一點雨  
今天來和小伙伴們聊一聊 Spring 中關(guān)于 Bean 別名的處理邏輯,別名,顧名思義就是給一個 Bean 去兩個甚至多個名字,整體上來說,在 Spring 中,有兩種不同的別名定義方式,感興趣的小伙伴跟著小編一起來看看吧

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()方法及其簡單的應用

    這篇文章主要介紹了關(guān)于重寫equals()方法和hashCode()方法及其簡單的應用,網(wǎng)上的知識有些可能是錯誤的,關(guān)于?equals()?方法的理解,大家討論不一樣,需要的朋友可以參考下
    2023-04-04
  • IDEA插件之mybatisx?插件使用教程

    IDEA插件之mybatisx?插件使用教程

    這篇文章主要介紹了mybatisx?插件使用教程,包括插件安裝自動生成代碼的相關(guān)知識,本文通過實例圖文相結(jié)合給大家介紹的非常詳細,需要的朋友可以參考下
    2022-05-05
  • JAVA比較兩張圖片相似度的方法

    JAVA比較兩張圖片相似度的方法

    這篇文章主要介紹了JAVA比較兩張圖片相似度的方法,涉及java針對圖片像素操作的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-07-07
  • springboot框架的全局異常處理方案詳解

    springboot框架的全局異常處理方案詳解

    這篇文章主要介紹了springboot框架的全局異常處理方案,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-03-03
  • SpringBoot進行多表查詢功能的實現(xiàn)

    SpringBoot進行多表查詢功能的實現(xiàn)

    這篇文章主要介紹了SpringBoot進行多表查詢功能,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-09-09
  • JAVA解析XML字符串簡單方法代碼案例

    JAVA解析XML字符串簡單方法代碼案例

    這篇文章主要介紹了JAVA解析XML字符串簡單方法代碼案例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-09-09
  • SpringMVC Interceptor攔截器使用教程

    SpringMVC Interceptor攔截器使用教程

    SpringMVC中攔截器(Interceptor)用于對URL請求進行前置/后置過濾,Interceptor與Filter用途相似,但實現(xiàn)方式不同。Interceptor底層就是基于Spring AOP 面向切面編程實現(xiàn)
    2023-01-01
  • Java面試題沖刺第二十四天--并發(fā)編程

    Java面試題沖刺第二十四天--并發(fā)編程

    這篇文章主要為大家分享了最有價值的三道關(guān)于數(shù)據(jù)庫的面試題,涵蓋內(nèi)容全面,包括數(shù)據(jù)結(jié)構(gòu)和算法相關(guān)的題目、經(jīng)典面試編程題等,感興趣的小伙伴們可以參考一下
    2021-08-08
  • java8新特性-lambda表達式入門學習心得

    java8新特性-lambda表達式入門學習心得

    這篇文章主要介紹了java8新特性-lambda表達式入門學習心得,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • 地址到經(jīng)緯度坐標轉(zhuǎn)化的JAVA代碼

    地址到經(jīng)緯度坐標轉(zhuǎn)化的JAVA代碼

    這篇文章介紹了地址到經(jīng)緯度坐標轉(zhuǎn)化的JAVA代碼,有需要的朋友可以參考一下
    2013-09-09

最新評論