概述
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>标签注册的别名,如果发生交叉,确实会改道。原因不详,需要看更多源码。