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

簡(jiǎn)單聊一聊Spring中Bean別名的處理原理

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

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)用

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

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

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

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

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

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

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

    SpringBoot進(jìn)行多表查詢(xún)功能的實(shí)現(xiàn)

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

    JAVA解析XML字符串簡(jiǎn)單方法代碼案例

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

    SpringMVC Interceptor攔截器使用教程

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

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

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

    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代碼

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

最新評(píng)論