最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

1. Alias

別名,顧名思義就是給一個(gè) Bean 去兩個(gè)甚至多個(gè)名字。整體上來說,在 Spring 中,有兩種不同的別名定義方式:

  • 定義 Bean 的 name 屬性,name 屬性在真正的處理過程中,實(shí)際上就是按照別名來處理的。
  • 通過 alias 標(biāo)簽定義專門的別名,通過 alias 定義出來的別名和 name 屬性定義的別名最終都是合并在一起處理的,所以這兩種定義別名的方式最終是殊途同歸。

那么定義的別名是保存在哪里呢?

大家知道,Bean 解析出來之后被保存在容器中,別名其實(shí)也是一樣的,容器中存在一個(gè) aliasMap 專門用來保存 Bean 的別名,保存的格式是 alias->name,例如有一個(gè) Bean 的名字是 user,別名是 userAlias,那么保存在 aliasMap 中就是 userAlias->user。

舉個(gè)簡單例子:

<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è)方法用來添加別名,核心邏輯就是向 aliasMap 中添加數(shù)據(jù)。
  • removeAlias:這個(gè)方法用來從 aliasMap 中移除一個(gè)別名。
  • isAlias:判斷給定的 name 是否是一個(gè)別名。
  • getAliases:根據(jù)給定的名字去獲取所有的別名。

方法就這四個(gè),看一下這個(gè)接口的實(shí)現(xiàn)類有哪些。

大家看到,AliasRegistry 的實(shí)現(xiàn)類其實(shí)還是蠻多的,但是大部分都是容器,真正實(shí)現(xiàn)了 AliasRegistry 中四個(gè)方法的只有 SimpleAliasRegistry,其他的容器大部分其實(shí)都是為了具備別名管理的能力,繼承了 SimpleAliasRegistry。

所以真正給我們整活的其實(shí)是 SimpleAliasRegistry。

2.2 SimpleAliasRegistry

SimpleAliasRegistry 類中的內(nèi)容比較多,為了講解方便,我就挨個(gè)貼屬性和方法出來,貼出來后和大家分享。

private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);

首先,SimpleAliasRegistry 中定義了一個(gè) aliasMap,這個(gè)就是用來保存別名的,這是一個(gè) Map 集合,接下來所有的操作都是圍繞這個(gè)集合展開。

@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è)方法用來移除別名,移除的思路很簡單,就是從 aliasMap 中移除數(shù)據(jù)即可,如果 remove 方法返回值為 null 那就說明要移除的別名不存在,那么直接拋出異常。

@Override
public boolean isAlias(String name) {
	return this.aliasMap.containsKey(name);
}

這個(gè)是判斷是否包含某一個(gè)別名,這個(gè)判斷簡單。有一個(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 查出來這個(gè) alias 所對應(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 查出來的 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 找到其對應(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è)方法用來檢查別名是否存在死結(jié),即 a 是 b 的別名,b 是 a 的別名這種情況。檢查的方式很簡單,就是調(diào)用 hasAlias 方法,但是將傳入的兩個(gè)參數(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);
			}
		});
	}
}

這個(gè)方法是處理別名是占位符的情況,例如當(dāng)引入了一個(gè) .properties 文件之后,那么在配置別名的時(shí)候就可以引用 .properties 中的變量,那么上面這個(gè)方法就是用來解析變量的。

例如下面這種情況,我有一個(gè) alias.properties,如下:

name=user
alias=javaboy

然后在 XML 文件中使用這個(gè) properties 文件,如下:

<context:property-placeholder location="classpath:alias.properties"/>
<alias name="${name}" alias="${alias}"/>

對于這種情況,一開始存入到 aliasMap 中的就是占位符了,resolveAliases 方法就是要將這些占位符解析為具體的字符串。

大家看到,首先這里將 aliasMap 復(fù)制一份,生成一個(gè) aliasCopy,然后進(jìn)行遍歷。在遍歷時(shí),根據(jù) valueResolver 將引用使用的占位符解析為真正的字符,如果解析出來的。如果解析出來的 name 和別名是相同的,那么顯然是有問題的,就需要把這個(gè)別名移除掉。

繼續(xù)判斷,如果解析出來的別名和原本的別名不相等(說明別名使用了占位符),那么就去檢查一下這個(gè)別名對應(yīng)的 name,如果這個(gè) name 已經(jīng)存在,且等于占位符解析出來的 name,說明這個(gè)別名已經(jīng)被定義過了,即重復(fù)定義,那么就把別名移除掉即可。如果這個(gè)別名指向的 name 和占位符解析出來的 name 不相等,說明試圖讓一個(gè)別名指向兩個(gè) bean,那么就直接拋出異常了。

如果解析出來的別名還沒有指向 name 屬性的話,那么就正常處理,檢查是否存在死結(jié)、移除帶占位符的別名,存入解析之后的別名。

最后,如果原本的名稱和解析之后的屬性名稱不相等,那么就直接保存這個(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è)就是使用最多的別名注冊了,傳入的參數(shù)分別是 bean 的 name 和 alias,如果 alias 跟 name 相等,二話不說直接移除,這個(gè) alias 有問題。

否則就去查詢這個(gè) alias,檢查這個(gè) alias 是否已經(jīng)有對應(yīng)的 name 了,如果有,且等于傳入的 name,那么直接返回就行了,不用注冊,因?yàn)橐呀?jīng)注冊過了;如果有且不等于傳入的 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è)方法用來解析出來別名里邊頂格的名字,例如有一個(gè) bean 有很多別名,a->b,b->c,c->d,那么這個(gè)方法的目的就是傳入 a、b、c 中任意一個(gè),返回 d 即可。因?yàn)?Spring 容器在處理的時(shí)候,并不用管這么多別名問題,容器只需要專注一個(gè)名字即可,因?yàn)樽詈笠粋€(gè)別名實(shí)際上就是指向真實(shí)的 beanId 了,所以最終拿到的 bean 名稱其實(shí)相當(dāng)于 bean 的 ID 了。

別名的處理主要就是這些方法。

3. 原理分析

前面我們說了,別名的來源主要是兩個(gè)地方:name 屬性和 alias 標(biāo)簽,我們分別來看。

3.1 name 處理

對于 name 屬性的處理,有兩個(gè)地方,一個(gè)是在 bean 定義解析的時(shí)候,將 name 屬性解析為 alias,具體在 BeanDefinitionParserDelegate#parseBeanDefinitionElement 方法中(這個(gè)方法在之前跟大家講 bean 的默認(rèn)名稱生成策略的時(shí)候,見過):

@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)中提取出來 name 屬性,然后切分為一個(gè)數(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 別名標(biāo)簽處理

別名的另一個(gè)來源是別名標(biāo)簽,在 Spring 解析 XML 標(biāo)簽的時(shí)候,有針對別名標(biāo)簽的專門處理,具體位置是在 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);
	}
}

這里會去判斷標(biāo)簽的類型,如果是別名,就調(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)簽上,提取出來 name 和 alias 屬性值,最后調(diào)用 registerAlias 方法進(jìn)行注冊。

以上就是簡單聊一聊Spring中Bean別名的處理原理的詳細(xì)內(nèi)容,更多關(guān)于Spring Bean別名處理的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • IDEA2024創(chuàng)建Web項(xiàng)目以及配置Tomcat的實(shí)現(xiàn)步驟

    IDEA2024創(chuàng)建Web項(xiàng)目以及配置Tomcat的實(shí)現(xiàn)步驟

    在Web項(xiàng)目的開發(fā)過程中,Tomcat作為一款開源的Servlet容器,扮演著至關(guān)重要的角色,本文將詳細(xì)闡述2024版本的idea配置Tomcat的全過程,下面就來詳細(xì)的介紹一下
    2025-10-10
  • Spring框架學(xué)習(xí)之AOP詳解

    Spring框架學(xué)習(xí)之AOP詳解

    這篇文章主要介紹了Spring框架學(xué)習(xí)之AOP詳解,文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)Spring框架的小伙伴們有一定的幫助,需要的朋友可以參考下
    2021-05-05
  • Java圖形界面之JFrame,JLabel,JButton詳解

    Java圖形界面之JFrame,JLabel,JButton詳解

    這篇文章主要介紹了Java圖形界面之JFrame、JLabel、JButton詳解,文中有非常詳細(xì)的代碼示例,對正在學(xué)習(xí)java的小伙伴們有非常好的幫助,需要的朋友可以參考下
    2021-04-04
  • 解決IDEA springboot

    解決IDEA springboot"spring-boot-maven-plugin"報(bào)紅問題

    這篇文章主要介紹了解決IDEA springboot"spring-boot-maven-plugin"報(bào)紅問題,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-04-04
  • Spring配置使用之Bean生命周期詳解

    Spring配置使用之Bean生命周期詳解

    這篇文章主要介紹了Spring配置使用之Bean生命周期詳解,具有一定參考價(jià)值,需要的朋友可以了解下。
    2017-10-10
  • java獲得平臺相關(guān)的行分隔符和java路徑分隔符的方法

    java獲得平臺相關(guān)的行分隔符和java路徑分隔符的方法

    不同系統(tǒng)平臺下的行分隔符、路徑分隔符等常常不同,如何在Java程序獲取當(dāng)前平臺的分隔符,以及其他系統(tǒng)相關(guān)的狀態(tài)呢?下面是示例程序,需要的朋友可以參考下
    2014-02-02
  • Java多線程之線程狀態(tài)詳解

    Java多線程之線程狀態(tài)詳解

    這篇文章主要介紹了Java多線程 線程狀態(tài)原理詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2021-09-09
  • 解析XML文件時(shí)的嵌套異常SAXParseException問題

    解析XML文件時(shí)的嵌套異常SAXParseException問題

    這篇文章主要介紹了解析XML文件時(shí)的嵌套異常SAXParseException問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • 排序算法圖解之Java插入排序

    排序算法圖解之Java插入排序

    插入排序是一種最簡單的排序方法,它的基本思想是將一個(gè)記錄插入到已經(jīng)排好序的有序表中,從而一個(gè)新的、記錄數(shù)增1的有序表。本文通過圖片合示例講解了插入排序的實(shí)現(xiàn),需要的可以了解一下
    2022-11-11
  • MyBatis中selectKey標(biāo)簽及主鍵回填實(shí)現(xiàn)

    MyBatis中selectKey標(biāo)簽及主鍵回填實(shí)現(xiàn)

    <selectKey>標(biāo)簽在MyBatis中提供了一種靈活的方式來生成和回填主鍵,本文就來介紹一下selectKey標(biāo)簽及主鍵回填實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-12-12

最新評論

山东省| 海安县| 龙州县| 赣榆县| 晋宁县| 清远市| 古蔺县| 辰溪县| 连云港市| 延安市| 精河县| 黄龙县| 红河县| 延川县| 洪江市| 屏南县| 潜山县| 岢岚县| 改则县| 平顶山市| 洞口县| 德保县| 太谷县| 巴东县| 门源| 四会市| 武乡县| 苍溪县| 黑山县| 商河县| 毕节市| 苗栗市| 杨浦区| 鄂州市| 阿瓦提县| 平南县| 巨鹿县| 高淳县| 武平县| 泗水县| 资阳市|