Spring 源码精读:SimpleAliasRegistry 别名的注册、解析与循环检测机制
Spring 源码精读SimpleAliasRegistry 别名的注册、解析与循环检测机制【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter导读别名Alias是 Spring IoC 容器中同一个 Bean 拥有多个可检索名称的底层支撑能力而SimpleAliasRegistry正是 Spring 容器中别名管理的核心实现类。本文以 docs/Spring/clazz/Spring-SimpleAliasRegistry.md 为主线逐方法拆解其aliasMap数据结构、别名注册与覆盖规则、别名环检测、传递式别名解析以及canonicalName归一化逻辑并结合仓库内 IoC 初始化与 BeanFactory 获取 Bean 的源码说明别名机制在 BeanDefinition 注册和依赖注入中的实际调用链路帮助读者彻底理解alias标签背后完整的工程实现。一、别名机制在 Spring 容器中的定位在阅读SimpleAliasRegistry之前先回答一个基础问题Spring 为什么需要别名在实际开发中同一个 Bean 往往需要被多个名字引用不同业务模块对同一个组件有各自的命名习惯如userService与userManager指向同一个 Bean通过 XML 的alias name... alias.../标签或注解方式为一个已注册 Bean 显式声明附加名称框架内部如 FactoryBean、占位符解析后的名称也需要一个统一入口把各种花名归一到真实 Bean 名称上。SimpleAliasRegistry就是负责管理这套名称 - 真实名称映射关系的核心实现它实现了org.springframework.core.AliasRegistry接口并被BeanDefinitionRegistry的实现类作为基类继承因此几乎所有 BeanDefinition 注册容器都天然具备别名能力。从继承关系看本仓库 docs/Spring/clazz/Spring-beanFactory.md 中展示的AbstractBeanFactory内部就维护着一个aliasMap字段正是SimpleAliasRegistry提供的别名注册表而 docs/Spring/IoC/3、将BeanDefinition注册进IoC容器.md 中的BeanDefinitionReaderUtils.registerBeanDefinition在注册完 BeanDefinition 后会调用registry.registerAlias(beanName, alias)把解析出的别名一并写入容器。二、顶层接口AliasRegistryAliasRegistry是 Spring 核心包中定义别名操作契约的接口位于org.springframework.core包。它声明了四个核心方法方法职责registerAlias(String name, String alias)注册别名即建立alias - name的映射若别名已被占用且不允许覆盖抛出IllegalStateExceptionremoveAlias(String alias)移除指定别名若别名不存在抛出IllegalStateExceptionisAlias(String name)判断给定名称是否是一个别名即是否存在于别名注册表中getAliases(String name)返回给定名称对应的全部别名数组无别名时返回空数组该接口的实现注释中特别注明了两个典型实现参照SimpleAliasRegistry与org.springframework.context.support.GenericApplicationContext。其中SimpleAliasRegistry提供了最基础的 Map 存储实现而GenericApplicationContext则在更上层的应用上下文中复用了这一能力。三、数据结构一张线程安全的 aliasMapSimpleAliasRegistry的核心状态只有一个字段/** * Map from alias to canonical name. * 存放别名的map(线程安全的), * 结构: alias- name */ private final MapString, String aliasMap new ConcurrentHashMap(16);关键点方向aliasMap的key 是别名value 是规范名canonical name即映射方向是别名 - 真实名称线程安全使用ConcurrentHashMap作为底层存储同时所有写操作registerAlias、removeAlias、getAliases、resolveAliases还会在synchronized (this.aliasMap)块内执行保证复合操作如先查后写的原子性初始容量构造时给定 16 的初始容量与容器加载场景下的默认规模匹配。对照仓库中 docs/Spring/clazz/Spring-beanFactory.md 展示的 XML 配置bean idfactory-bean-user classorg.source.hot.spring.overview.ioc.bean.init.UserFactoryBean/ alias namefactory-bean-user aliasuserFactoryBean/其最终在aliasMap中呈现的映射关系为alias标签的alias属性值作为 key别名name属性值作为 value真实 Bean 名即userFactoryBean - factory-bean-user。四、注册别名registerAlias 的完整处理流程registerAlias是别名机制中最核心的方法其完整逻辑如下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)) { // 相同在别名map中移除 this.aliasMap.remove(alias); if (logger.isDebugEnabled()) { logger.debug(Alias definition alias ignored since it points to same name); } } else { // 不相同 // 从map对象中获取别名为alias的value String registeredName this.aliasMap.get(alias); if (registeredName ! null) { // 判断map中是否有有一个别名和传入的name相同的内容 if (registeredName.equals(name)) { // An existing alias - no need to re-register return; } if (!allowAliasOverriding()) { 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); // 放入 map 对象中 alias- name this.aliasMap.put(alias, name); if (logger.isTraceEnabled()) { logger.trace(Alias definition alias registered for name name ); } } } }整个流程可以分为五个分支逐一分析4.1 参数校验Assert.hasText保证name与alias都不能为空白字符串这是 Spring 中经典的防御式编程手法。4.2 分支一别名与名称相同当alias.equals(name)成立时说明用户试图把一个名称注册成它自己的别名这是无意义的。此时 Spring 的做法是从aliasMap中移除该键如果之前存在同名键的话并记录一条 debug 日志Alias definition xxx ignored since it points to same name。这种自引用即忽略的处理避免了容器中出现a - a这种冗余映射。4.3 分支二已存在相同映射先通过this.aliasMap.get(alias)取出该别名当前指向的规范名registeredName。若registeredName.equals(name)说明alias - name的映射已经存在此时直接return不重复注册属于幂等处理。4.4 分支三别名冲突与覆盖策略若registeredName非空且不等于name说明该别名已被其他名称占用此时取决于allowAliasOverriding()的返回值默认实现返回true允许覆盖日志记录 Overriding alias ...子类可覆写该方法返回false此时抛出IllegalStateExceptionCannot define alias ... It is already registered for name ...。这一设计将是否允许覆盖的策略开放给了子类是典型的模板方法模式应用。4.5 分支四别名环检查// 别名环检查 checkForAliasCircle(name, alias);在真正写入前Spring 会预先检测循环别名详见第六节一旦发现name已经是alias的直接或间接别名立即抛出IllegalStateException防止容器出现a - b、b - a这样的死循环引用。4.6 分支五写入映射通过环检查后执行this.aliasMap.put(alias, name)完成注册并输出一条 trace 级别的日志。五、是否允许覆盖allowAliasOverriding/** * Return whether alias overriding is allowed. * Default is {code true}. * 是否允许重写别名 */ protected boolean allowAliasOverriding() { return true; }该方法是受保护的空壳方法默认允许覆盖。它的意义在于将策略决策点暴露给子类。例如DefaultListableBeanFactory等在别名管理之上叠加 Bean 定义覆盖策略的容器可以在子类中覆写此方法以收紧或放开别名覆盖权限。六、循环别名检测checkForAliasCircle 与 hasAlias6.1 checkForAliasCircle/** * Check whether the given name points back to the given alias as an alias * in the other direction already, catching a circular reference upfront * and throwing a corresponding IllegalStateException. */ 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); } }它调用hasAlias(alias, name)如果name是alias的别名成立说明二者互为别名构成循环引用直接抛异常拦截。6.2 hasAlias 的递归判断/** * Determine whether the given name has the given alias registered. * 递归判断是否已经存在别名 */ public boolean hasAlias(String name, String alias) { for (Map.EntryString, String entry : this.aliasMap.entrySet()) { // 获取key值 String registeredName entry.getValue(); if (registeredName.equals(name)) { String registeredAlias entry.getKey(); // 循环引用判断 if (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias)) { return true; } } } return false; }hasAlias(name, alias)的语义是alias是否是name的直接或间接别名。实现思路是遍历整个aliasMap找到所有值等于name的条目即name的直接别名registeredAlias然后判断直接命中registeredAlias.equals(alias)间接命中递归调用hasAlias(registeredAlias, alias)顺着别名链继续向下找。这种方法保证了别名可以多级传递如a - b、b - c则a是c的间接别名同时能够完整探测出环的存在。七、移除与查询removeAlias / isAlias / getAliases7.1 removeAliasOverride public void removeAlias(String alias) { synchronized (this.aliasMap) { // 判断是否移除成功 String name this.aliasMap.remove(alias); if (name null) { throw new IllegalStateException(No alias alias registered); } } }直接按 key 移除若返回值为null说明该别名从未注册抛出IllegalStateException。这里对 Map 操作加锁保证与其他写操作互斥。7.2 isAliasOverride public boolean isAlias(String name) { return this.aliasMap.containsKey(name); }判断标准非常直接key 集合中是否包含该名称。因为aliasMap的 key 全部是别名所以只要 key 命中就说明它是别名。若一个名称从未出现在 key 中即使它作为某些别名的 value 存在也不算别名。7.3 getAliases 与 retrieveAliasesOverride public String[] getAliases(String name) { ListString result new ArrayList(); synchronized (this.aliasMap) { retrieveAliases(name, result); } return StringUtils.toStringArray(result); } private void retrieveAliases(String name, ListString result) { // 循环获取 this.aliasMap.forEach((alias, registeredName) - { if (registeredName.equals(name)) { result.add(alias); // 递归查询循环引用的别名 retrieveAliases(alias, result); } }); }getAliases返回指定名称的全部别名其核心在私有方法retrieveAliases遍历aliasMap凡是 value 等于name的 key 都是直接别名加入结果集对每个直接别名再次递归调用retrieveAliases把别名链上的所有间接别名也一并收集。例如映射a - b、b - c时getAliases(c)会返回[b, a]。这种传递式收集与第六节的hasAlias递归检测形成了对称设计一个负责查询、一个负责防环。八、占位符解析resolveAliasespublic void resolveAliases(StringValueResolver valueResolver) { Assert.notNull(valueResolver, StringValueResolver must not be null); synchronized (this.aliasMap) { MapString, 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)) { // Pointing to existing alias - just remove placeholder 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); } }); } }resolveAliases用于在容器刷新阶段对别名和规范名中的占位符进行解析。例如 Bean 名称中含有${...}占位符时StringValueResolver会将其解析为真实值。其处理分支解析后失效解析结果为null或解析后别名与名称相同则移除原别名别名本身变化解析后的别名与原始别名不同需要先检查新别名是否被占用占用且指向不同名称则抛异常再做环检测最后执行删除旧键、写入新键的搬迁仅规范名变化别名不变但指向的规范名变了直接put更新 value。注意它在synchronized块内先拷贝了一份aliasCopy再遍历避免在遍历过程中修改aliasMap引发ConcurrentModificationException。九、核心归一化方法canonicalName/** * Determine the raw name, resolving aliases to canonical names. */ public String canonicalName(String name) { String canonicalName name; // Handle aliasing... String resolvedName; do { resolvedName this.aliasMap.get(canonicalName); if (resolvedName ! null) { canonicalName resolvedName; } } while (resolvedName ! null); return canonicalName; }canonicalName是别名机制中被调用最频繁的方法从任意名称出发沿别名链一直解析直到找到最底层的真实 Bean 名。如果name不是别名aliasMap.get返回null直接返回原名称如果name是别名则取出其指向的名称继续以该名称为 key 查询循环往复由于注册阶段已经通过checkForAliasCircle排除了环因此这里的do...while循环必然终止。典型的调用链路来自 docs/Spring/clazz/Spring-beanFactory.md 中的AbstractBeanFactory#transformedBeanNameprotected String transformedBeanName(String name) { // 转换 beanName . // 1. 通过 BeanFactoryUtils.transformedBeanName 求beanName // 2. 如果是有别名的(方法参数是别名) . 会从别名列表中获取对应的 beanName return canonicalName(BeanFactoryUtils.transformedBeanName(name)); }该链路展示了getBean(userFactoryBean)这类用别名获取 Bean的调用会先剥掉前缀FactoryBean 约定再通过canonicalName将别名归一为factory-bean-user这样的真实 Bean 名最终完成单例查找。十、在 IoC 容器中的实际调用链别名机制不是孤立的工具类它贯穿整个 IoC 注册与获取流程10.1 BeanDefinition 注册阶段别名写入在 docs/Spring/IoC/3、将BeanDefinition注册进IoC容器.md 中DefaultBeanDefinitionDocumentReader.processBeanDefinition将bean元素解析为BeanDefinitionHolder其内部持有beanName与aliases别名数组随后调用BeanDefinitionReaderUtils.registerBeanDefinition// 获取解析的 bean元素 的名称 beanName String beanName definitionHolder.getBeanName(); // 开始向 IoC容器 注册 BeanDefinition对象 registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // 如果解析的 bean元素 有别名alias向容器中注册别名 String[] aliases definitionHolder.getAliases(); if (aliases ! null) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } }这里registry.registerAlias(beanName, alias)的调用最终会落到SimpleAliasRegistry#registerAlias因为DefaultListableBeanFactory继承自SimpleAliasRegistry从而把 XML 中bean name... alias.../或alias标签声明的别名写入aliasMap。同样docs/Spring/clazz/Spring-BeanDefinitionReaderUtils.md 中对应源码片段Register aliases for bean name, if any也印证了注册 BeanDefinition 后立即注册其别名的标准流程。10.2 Bean 获取阶段别名归一在getBean(别名)时AbstractBeanFactory会先调用transformedBeanName把别名转成规范名再执行单例缓存查询。由此可知别名机制让任意合法名称都能命中同一个 Bean 实例同时因为所有别名最终都归一为同一个规范名单例缓存中不会产生重复实例。十一、方法全景一览将SimpleAliasRegistry的全部方法汇总如下便于速查方法类型功能registerAlias(String name, String alias)public注册别名含幂等判断、覆盖策略与环检测allowAliasOverriding()protected是否允许覆盖已有别名默认true可被子类覆写hasAlias(String name, String alias)public递归判断alias是否是name的直接或间接别名removeAlias(String alias)public移除别名不存在则抛异常isAlias(String name)public判断名称是否为别名key 命中即真getAliases(String name)public返回全部直接与间接别名数组retrieveAliases(String name, List\String\ result)private递归收集别名的核心实现resolveAliases(StringValueResolver valueResolver)public对别名与规范名中的占位符做统一解析checkForAliasCircle(String name, String alias)protected注册前检测循环别名并抛异常canonicalName(String name)public沿别名链解析返回最终规范名十二、总结通过源码级的逐方法分析可以看出SimpleAliasRegistry虽是一个不足 400 行的基础类却完整承载了 Spring 别名管理的全部核心语义单向映射aliasMap采用别名 - 规范名方向存储配合ConcurrentHashMap与synchronized双重保障线程安全注册策略自引用忽略、重复注册幂等、冲突按allowAliasOverriding决定覆盖或抛异常环检测checkForAliasCircle 递归版hasAlias在写入前拦截循环别名保证别名链永远是一棵有向无环的引用树传递解析getAliases/retrieveAliases递归收集间接别名canonicalName递归归一化二者配合完成任意别名 - 唯一规范名的双向旅程占位符支持resolveAliases借助StringValueResolver支持名称中的占位符解析容器落地从BeanDefinitionReaderUtils.registerBeanDefinition的注册入口到AbstractBeanFactory.transformedBeanName的获取入口SimpleAliasRegistry的能力被DefaultListableBeanFactory、GenericApplicationContext等核心容器继承复用。理解了SimpleAliasRegistry就打通了 Spring IoC 中名称解析这一环后续再看到alias标签、Bean(name ...)的多名称声明或是getBean传别名时的内部流转都能清晰地还原出其底层的数据结构与算法逻辑。相关的兄弟类还包括 Spring-DefaultSingletonBeanRegistry.md单例注册表与 Spring-BeanDefinitionReaderUtils.mdBeanDefinition 注册工具可一并阅读以构建完整的容器注册体系认知。【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址: https://gitcode.com/GitHub_Trending/so/source-code-hunter创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考