64 Star 426 Fork 156

huifer / Code-Analysis

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
第五章-别名注册.md 10.05 KB
一键复制 编辑 原始数据 按行查看 历史
huifer 提交于 2021-02-18 16:46 . fix: 修改图片地址

第五章 别名注册

  • 本章笔者将和各位读者介绍别名标签 alias 解析中重要的一环: 别名注册. 这一环是将 XML 配置文件中的 alias 标签信息放在一个容器中建立映射关系。

第三章中笔者简单介绍了关于 alias 标签的处理和 alias 标签的处理(别名注册) 在第三章中讲述的内容相对比较浅显, 在这一章中笔者将会详细的介绍别名注册。

5.1 环境搭建

  • 本章节的环境搭建过程和第三章的 alias 搭建过程一致,各位可以直接延用第三章的工程进行测试。搭建过环境的笔者可以不看本节,搭建的读者可以阅读本节
  1. 首先创建 Spring XML 配置文件 ,将文件命名成 alias-node.xml 并向其填充代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns="http://www.springframework.org/schema/beans"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


   <alias name="people" alias="p1"/>
   <bean id="people" class="com.source.hot.ioc.book.pojo.PeopleBean">
       <property name="name" value="zhangsan"/>
   </bean>
</beans>
  1. 测试用例编写,
class AliasNodeTest {

   @Test
   void testAlias() {
       ClassPathXmlApplicationContext context
               = new ClassPathXmlApplicationContext("META-INF/alias-node.xml");

       Object people = context.getBean("people");
       Object p1 = context.getBean("p1");

       assert people.equals(p1);
   }
}

这就是第三章中的一个基本用例,本章继续使用。

在开始分析注册之前我们还是需要将核心方法找出来,先找到解析 alias 标签的方法: org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#processAliasRegistration,在 processAliasRegistration 方法中大体流程笔者在第三章已经做了一个讲述了,本章会围绕 getReaderContext().getRegistry().registerAlias(name, alias) 这段代码进行分析。

5.2 别名注册接口

  • 在前文我们确定了需要分析的目标方法, 下面我们需要找到方法提供者。

getReaderContext().getRegistry().registerAlias(name, alias) 方法中我们可以确定方法的定义者是 : AliasRegistry

在 Spring 中关于 AliasRegistry 接口的实现类有很多,具体实现列表请看下图

image-20210106131003948

这里对于大多数读者来说可能会迷茫,这么多实现类我应该去找哪一个看呢?笔者在这里给大家一个建议,在前文我们准备好了一个测试环境,同时我们也找到了需要分析的方法入口,打上断点单步调试就可以找到具体的方法实现了。

笔者在这里直接告诉各位读者真正的调用方法是由 SimpleAliasRegistry 提供的。

5.3 SimpleAliasRegistry 中注册别名的实现、

  • 首先笔者将整个注册别名的方法全部贴出来, 各位读者请先阅读.
// 删除日志和验证相关代码
public void registerAlias(String name, String alias) {
    synchronized(this.aliasMap) {
        // 别名和真名是否相同
        if(alias.equals(name)) {
            // 移除
            this.aliasMap.remove(alias);
        } else {
            // 通过别名获取真名
            String registeredName = this.aliasMap.get(alias);
            // 真名不为空
            if(registeredName != null) {
                // 真名等于参数的真名
                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 + "'.");
                }
            }
            // 别名是否循环使用
            checkForAliasCircle(name, alias);
            // 设置 别名对应真名
            this.aliasMap.put(alias, name);
        }
    }
}

先来看参数

  1. name: 这个参数是 alias 标签中属性 name 的数据值, 一般情况下笔者将其称之为真名、BeanName
  2. alias : 这个参数是 alias 标签中属性 alias 的数据值, 一般情况下笔者将其称之为别名

接下来我们来看看方法的细节

整个方法以这样一个判断作为分支: 判断别名是否和真名相等,相等时候的处理很简单,从别名容器中剔除这个别名的键值信息。

接下来就是对于别名和真名不相等的情况处理了。

第一步:从别名容器中用别名去获取容器中可能存在的真名,

第二步:当容器中可能存在的真名存在的情况下会和参数真名进行比较是否相同 , 相同的话就直接返回了, 不做其他处理。

第三步:当容器中可能存在的真名不存在的情况下 Spring 会进行别名是否循环使用,当检测通过时加入到别名容器中。

整个流程中比较复杂的方法在 checkForAliasCircle 中,其他的步骤相对来说是一个比较容易理解的流程。 下面我们来对别名检查做一个分析

先看 checkForAliasCircle 的代码

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 方法重点都在这个方法中了

public boolean hasAlias(String name, String alias) {
   // 从别名map中获取已注册的真名
   String registeredName = this.aliasMap.get(alias);
   // 注册的真名和 参数真名是否相同,
   // 递归判断是否存在别名
   return ObjectUtils.nullSafeEquals(registeredName, name) || (registeredName != null
         && hasAlias(name, registeredName));
}

hasAlias 中的验证规则除了递归以外,我们来看它到底做了什么验证。

从别名容器中获取当前参数 alias 对应的容器内真名 (registeredName),将 registeredNamename 做比较确认是否是相等的。递归也就是递归这一段内容了, 这就是验证别名的完整过程。

到此我们对于别名注册的方法已经了解了. 下面提出一个疑问: 当我们在使用别名去获取 Bean 对象的时候 Spring 是如何找到真正的名字的? 这就是我们下面要进行分析的问题. 各位读者可以思考一下翻一下代码。

5.4 别名换算真名

首先告诉大家下面这个问题的答案。

当我们在使用别名去获取 Bean 对象的时候 Spring 是如何找到真正的名字的?

在 Spring 中有一个叫做 transformedBeanName 的方法, 提供者是 AbstractBeanFactory 这个方法就是专门用来求解 Bean Name 的方法! ( 方法签名: org.springframework.beans.factory.support.AbstractBeanFactory#transformedBeanName )

在了解了获取方法后先不着急看代码, 继续思考一个问题, 当我们有别名的时候从哪里获得对应的 Bean Name ? 这个问题的答案当然还是从我们的别名容器 ( aliasMap )中去获取,这个容器的 kv 就是 alias 和 Bean Name 的关系。

现在我们来看完整的代码

  • transformedBeanName 方法详情
protected String transformedBeanName(String name) {
   // 转换 beanName .
   // 1. 通过·BeanFactoryUtils.transformedBeanName· 求beanName
   // 2. 如果是有别名的(方法参数是别名) . 会从别名列表中获取对应的 beanName
   return canonicalName(BeanFactoryUtils.transformedBeanName(name));
}

transformedBeanName 中得分两步看

  1. 传入参数的处理
  2. 别名容器中获取对应的 Bean Name

那么我们就按照代码执行顺序先来看参数的处理: BeanFactoryUtils.transformedBeanName(name) 做了什么

  • BeanFactoryUtils#transformedBeanName 方法详情
public static String transformedBeanName(String name) {
   Assert.notNull(name, "'name' must not be null");
   // 名字不是 & 开头直接返回
   if (!name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX)) {
      return name;
   }
   // 截取字符串 在返回
   return transformedBeanNameCache.computeIfAbsent(name, beanName -> {
      do {
         beanName = beanName.substring(BeanFactory.FACTORY_BEAN_PREFIX.length());
      }
      while (beanName.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));
      return beanName;
   });
}

在这个方法中根据参数是否是 & 字符串开头进行两种处理

  1. 不是 & 开头直接返回参数
  2. & 开头切割字符串后返回

在这个方法中得到的都是一个字符串, 可能是别名, 可能是 Bean Name

回归到我们的测试用例 p1 是我们的别名, 这个别名不存在 & 开头那这里就直接返回 p1 了.

这样我们就理解了第一步 BeanFactoryUtils.transformedBeanName(name) 的作用, 下面就是 canonicalName 方法的分析了。

首先找到方法的提供者 SimpleAliasRegistry#canonicalName

  • SimpleAliasRegistry#canonicalName 方法详情
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;
}

这就是一个简单的处理了. 直接从容器中获取对应的名称返回即可。

  • 至此就是别名换算真名的完整过程了

5.5 总结

在本章节中笔者向各位介绍了别名注册和别名使用的源码分析,它们分别位于 AliasRegistrySimpleAliasRegistryBeanFactoryUtils 之中。

Java
1
https://gitee.com/pychfarm_admin/code-analysis.git
git@gitee.com:pychfarm_admin/code-analysis.git
pychfarm_admin
code-analysis
Code-Analysis
master

搜索帮助