Spring实体Bean的别名注册之SimpleAliasRegsitry

概述

SimpleAliasRegsitry实现了AliasRegistry接口,主要作用是支持实体Bean的别名注册。

SimpleAliasRegustry源码

属性:

	/**别名 -> 真实名(或别名),注意,value值不一定都是Bean对应的id */
	private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);

首先需要说明的是,spring的Xml配置文件是支持的,在alias的name属性可以填写bean的id,也可以填写bean的其它alias

	<bean id="testBean" name="A" class="org.springframework.tests.sample.beans.TestBean" scope="prototype">
		<property name="name"><value>custom</value></property>
		<property name="age"><value>25</value></property>
	</bean>
	<alias name="testBean" alias="B"/>
	<alias name="A" alias="C"/>
	<alias name="B" alias="D"/>

指向关系如图:
在这里插入图片描述
需要注意,这个map存有的是多个Bean的别名与id,一条链沿箭头方向最末端就是id,中间的为alias。

主要方法:

1.别名的注册:registryAlias
	/**
	1:别名和规范名相同,则删除
	*/
	@Override
	public void registerAlias(String name, String alias) {
		Assert.hasText(name, "'name' must not be empty");
		Assert.hasText(alias, "'alias' must not be empty");
		synchronized (this.aliasMap) {
			if (alias.equals(name)) {//注册名与别名相同,则删除注册
				this.aliasMap.remove(alias);
				if (logger.isDebugEnabled()) {//记录日志
					logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
				}
			}else {//正常的注册
				String registeredName = this.aliasMap.get(alias);//别名是否已经注册
				if (registeredName != null) {
					if (registeredName.equals(name)) {//映射关系已注册
						return;
					}
					if (!allowAliasOverriding()) {//alias先后指向不同的名,允许重载则指向最后名,不允许报异常
						throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +
								name + "': It is already registered for name '" + registeredName + "'.");
					}
					if (logger.isDebugEnabled()) {//记录日志
						logger.debug("Overriding alias '" + alias + "' definition for registered name '" +
								registeredName + "' with new target name '" + name + "'");
					}
				}
				checkForAliasCircle(name, alias);//检查别名循环
				this.aliasMap.put(alias, name);//注册
				if (logger.isTraceEnabled()) {
					logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'");
				}
			}
		}
	}

此方法用来注册别名与规范名,或别名与别名之间的关系,中间有别名,循环指向的检查,如果有循环存在,则无法确定哪一个是id。具体的检查逻辑如下:

	/**
	循环校验,A -> B -> C,如果想要添加C到A的映射,需要先校验A是否直接或间接的指向C,若果是,则不允许添加,
	否则会发生别名循环
	*/
	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");
		}
	}

	/**
	用来检查name,是否有被某一个alias直接或间接的指向,如A -> B -> C,此时A已经通过B间接指向C,如果想要添加映射A ->C,方法返回
	true,表名A到C的映射已经注册
	*/
	public boolean hasAlias(String name, String alias) {
		for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) {
			String registeredName = entry.getValue();
			if (registeredName.equals(name)) {//名字已经注册,需要检查循环指向
				String registeredAlias = entry.getKey();
				if (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias)) {
					return true;
				}
			}
		}
		return false;
	}

还需要注意的一点是,HashMap的put方法,在key相等时,会发生覆盖,所以如果配置文件中的多个Bean的alias之间有交叉,可能会发生覆盖,即原有映射链改道,改道之后可能指向另一个规范名(id),从而获得无法获得正确的Bean,发生异常。例如:配置文件如下会发生什么?

	<bean id="testBean" name="A" class="org.springframework.tests.sample.beans.TestBean" scope="prototype">
		<property name="name"><value>custom</value></property>
		<property name="age"><value>25</value></property>
	</bean>
	<alias name="testBean" alias="B"/>
	<alias name="A" alias="C"/>
	<alias name="B" alias="D"/>
	<bean id="pet" name="B" class="org.springframework.tests.sample.beans.Pet" scope="prototype">
		<property name="name"><value>dog</value></property>
	</bean>
	<alias name="B" alias="E"/>

过程可由下图表示:
在这里插入图片描述
测试代码和结果如下:
在这里插入图片描述
在这里插入图片描述
可以看到,确实别名B注册到标准名pet下。另外需要注意,方法在用做删除别名时,十分危险,例如,在上述配置文件基础上增加别名配置 < alias name=“B” alias=“B”/>,此时运行上面的测试会包如下错误:
在这里插入图片描述
原因很明显,此时B处于链方向的末端,被认为是id,去查找对应的对象,当然找不到。

2 根据name获取alias集合:getAlias
	@Override
	public String[] getAliases(String name) {
		List<String> result = new ArrayList<>();
		synchronized (this.aliasMap) {
			retrieveAliases(name, result);
		}
		return StringUtils.toStringArray(result);
	}

	/**
	 根据name将上游的alias全部找出:
	 如果传入的是id(在最下游),返回的是alias的全集
	 传出的是alias,返回的是还alias的上游别名集合
	 */
	private void retrieveAliases(String name, List<String> result) {
		this.aliasMap.forEach((alias, registeredName) -> {
			if (registeredName.equals(name)) {
				result.add(alias);
				retrieveAliases(alias, result);
			}
		});
	}
3根据alias获得id(规范名):canonicalName
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;
	}
4其他方法:
public boolean isAlias(String name)//判断是否是别名
public void removeAlias(String alias)//删除单一别名
public void resolveAliases(StringValueResolver valueResolver)

最后一个方法要求传入一个字符串的解析器,进行字符串的解析、规范化,方法内部会将aliasMap中所有alias和id进行解析、规范化,然后将相应的解析结果按照原有的映射关系重新,进行验证然后映射。验证内容与原有注册时的验证规则一致。

总结:

小结:
SimpleAliasRegistry类十分小巧,通过对其源码的解读,了解了别名注册的过程,也发现了需要注意的几个问题:
1:别名交叉改道问题;(alias属性交叉改道,通过name属性注册的别名如果交叉,在解析文件的时候就会抛出异常)
2:同名删除可能导致alias被错认为id(规范名),以alias作为id去查找对应的实体对象时获取不到。
建议:
最好不用别名alias
如果在配置文件中用到别名,需要注意以上问题;
思考:
1:一个bean的id(规范名)以alias(别名)身份指向另一个bean的id(规范名),会发生什么?是不是类似交叉改道?
结果是:BeanDefinitionParsingException,名称已经被占用(在BeanDefinitionParserDelegate处抛出)。
2:两种注册别名的方式是否一样呢?(< bean>标签的name属性,和< alias>标签的alias属性)?
细节处不一致,通过< bean>的name属性定义得别名,如果发生交叉,同问题1,解析文件时就报错。而通过< alias>标签注册的别名,如果发生交叉,确实会改道。原因不详,需要看更多源码。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值