前言
在 spring 容器中,允许通过名称或别名来获取 bean ,这个能力来自于顶层接口 AliasRegistry,分析类下属的关系图,可以看到,几乎所有主要容器都直接或间接的实现了 AliasRegistry 接口。
AliasRegistry 的结构非常简单,主要的类就是 AliasRegistry 接口与他的实现类 SimpleAliasRegistry,后续的实现类基本都直接或间接的继承了 SimpleAliasRegistry。
本文将基于 spring 源码 5.2.x 分支,围绕 SimpleAliasRegistry 解析 spring 的别名机制是如何实现的。
一、AliasRegistry
在 spring 的容器中,一个 bean 必须至少有一个名称,而一个名称可以有多个别名,别名亦可以有别名,若我们把这个最原始的名称称为 id,则结构可以有:
1 | id -> id's alias1 -> alias of id's alias1 ... ... |
通过 bean 的 id,或与该 id 直接、间接相关的别名,都可以从容器中获取到对应的 bean。
AliasRegistry 的作用就是定义这一套规则:
1 | public interface AliasRegistry { |
二、SimpleAliasRegistry
SimpleAliasRegistry 是直接基于 AliasRegistry 接口提供的简单实现类,他在内部维护了一个 Map 集合,以别名作为 key,别名对应的原名称作为 value:
1 | public class SimpleAliasRegistry implements AliasRegistry { |
在 SimpleAliasRegistry 中,我们在上文所提到的 id ,被称为标准名称 CanonicalName。
1、注册别名
1 |
|
是否允许覆盖别名对应的原名称
在 registerAlias 方法中,调用了 allowAliasOverriding 方法,该方法在 SimpleAliasRegistry 中固定返回 true :
1 | protected boolean allowAliasOverriding() { |
该方法决定一个已经有对应原名称的别名,是否允许被重新被对应到另一个原名称。跟 HashMap 中的 afterNodeXXX 方法一样,这里很明显是留给子类重写的钩子方法。
检查是否存在循环引用
在 registerAlias 方法中,调用了 checkForAliasCircle 以检查别名是否存在 a -> b -> a 这样的循环引用:
1 | protected void checkForAliasCircle(String name, String alias) { |
因为在 SimpleAliasRegistry 中,一个名称作为另一个名称的别名后,该名称仍然允许有别的名称作为它的别名。
举个例子,实际场景中,可能会出现 a -> b -> c 这种情况,对应在 aliasMap 中则存在 b -> a 与 c -> b 的引用关系。
此时,若想要确定 c 是否是 a 的别名,就需要在通过 c 取出 b 后,再向上递归根据 b 找到 a,把每一级的关系却判断一遍。
为什么要加锁
理解了循环引用的检查,也就不难理解加锁的必要性了。实际使用中,SimpleAliasRegistry 基本是作为单例使用,此时不可避免的可能会存在多线程调用 registerAlias 的可能性,即便 aliasMap 使用的 ConcurrentHashMap 本身是线程安全的,但是这不能阻止出现 a -> b 和 b -> a 两个操作的同时发生,此时 a 与 b 构成了循环引用,而每个线程中的 checkForAliasCircle 却未必能够正确的检测出问题。
2、获取名称
获取全部别名
1 |
|
获取别名也很好理解,首先加锁同样的为了避免删除的同时注册别名导致的一些问题,然后递归调用的 retrieveAliases 也是为了防止出现“别名还有别名”的情况,getAliases 最终将返回该名称与该名称构成直接或间接引用关系的别名。
获取标准名称
1 | public String canonicalName(String name) { |
该方法的作用是通过一个别名获得对应的标准名称。举个例子,假如现在存在如下别名引用关系:a -> b -> c -> d,则从 a、b、c、d 任意一个名称开始调用该方法,则最终都会返回 a。
3、移除别名
1 |
|
4、名称转换
有时候,直接注册到 SimpleAliasRegistry 的名称和对应别名未必就是最想要,还需要替换占位符或者进行一些大小写转换的操作,这就需要通过 resolveAliases 方法来完:
1 | public void resolveAliases(StringValueResolver valueResolver) { |
其中,StringValueResolver 是一个函数式接口,它用于将一个字符串转为另一个字符串,也就是 resolve 的过程:
1 |
|
resolveAliases 的过程基本与注册别名的方法 registerAlias 一致,本质上就是将原有的别名的与名称转换后再次建立映射关系,然后移除旧的映射关系。