这个方法是判断给定的 name 和 alias 之间是否具备关联关系。判断的逻辑就是先去 aliasMap 中,根据 alias 查出来这个 alias 所对应的真实 beanName,即 registeredName,然后判断 registeredName 和 name 是否相等,如果相等就直接返回,如果不相等就继续递归调用,为什么要递归呢?因为 aliasMap 中存在的别名可能是这样的:
a->b
b->c
c->d
即 a 是 b 的别名,b 是 c 的别名,c 是 d 的别名,现在如果想要判断 a 和 d 之间的关系,那么根据 a 查出来的 b 显然不等于 d,所以要继续递归,再根据 b 查 c,根据 c 查到 d,这样就能确定 a 和 d 是否有关系了。
getAliases 方法是根据传入的 name 找到其对应的别名,但是由于别名可能存在多个,所以调用 retrieveAliases 方法递归去查找所有的别名,将找到的别名都存入到一个集合中,最终将集合转为数组返回。
1 2 3 4 5 6 7
protectedvoidcheckForAliasCircle(String name, String alias) { if (hasAlias(alias, name)) { thrownewIllegalStateException("Cannot register alias '" + alias + "' for name '" + name + "': Circular reference - '" + name + "' is a direct or indirect alias for '" + alias + "' already"); } }
这个方法用来检查别名是否存在死结,即 a 是 b 的别名,b 是 a 的别名这种情况。检查的方式很简单,就是调用 hasAlias 方法,但是将传入的两个参数颠倒过来就可以了。
大家看到,首先这里将 aliasMap 复制一份,生成一个 aliasCopy,然后进行遍历。在遍历时,根据 valueResolver 将引用使用的占位符解析为真正的字符,如果解析出来的。如果解析出来的 name 和别名是相同的,那么显然是有问题的,就需要把这个别名移除掉。
继续判断,如果解析出来的别名和原本的别名不相等(说明别名使用了占位符),那么就去检查一下这个别名对应的 name,如果这个 name 已经存在,且等于占位符解析出来的 name,说明这个别名已经被定义过了,即重复定义,那么就把别名移除掉即可。如果这个别名指向的 name 和占位符解析出来的 name 不相等,说明试图让一个别名指向两个 bean,那么就直接抛出异常了。
如果解析出来的别名还没有指向 name 属性的话,那么就正常处理,检查是否存在死结、移除带占位符的别名,存入解析之后的别名。
@Override publicvoidregisterAlias(String name, String alias) { synchronized (this.aliasMap) { if (alias.equals(name)) { this.aliasMap.remove(alias); } else { StringregisteredName=this.aliasMap.get(alias); if (registeredName != null) { if (registeredName.equals(name)) { return; } if (!allowAliasOverriding()) { thrownewIllegalStateException("Cannot define alias '" + alias + "' for name '" + name + "': It is already registered for name '" + registeredName + "'."); } } checkForAliasCircle(name, alias); this.aliasMap.put(alias, name); } } }
这个就是使用最多的别名注册了,传入的参数分别是 bean 的 name 和 alias,如果 alias 跟 name 相等,二话不说直接移除,这个 alias 有问题。
否则就去查询这个 alias,检查这个 alias 是否已经有对应的 name 了,如果有,且等于传入的 name,那么直接返回就行了,不用注册,因为已经注册过了;如果有且不等于传入的 name,那么就抛出异常,因为一个 alias 不能指向两个 name。最后就是检查和保存了。
1 2 3 4 5 6 7 8 9 10 11 12
public String canonicalName(String name) { StringcanonicalName= 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 的默认名称生成策略的时候,见过):
protectedvoidprocessAliasRegistration(Element ele) { Stringname= ele.getAttribute(NAME_ATTRIBUTE); Stringalias= ele.getAttribute(ALIAS_ATTRIBUTE); booleanvalid=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 属性值,最后调用 registerAlias 方法进行注册。