alias
解析中重要的一环: 别名注册. 这一环是将 XML 配置文件中的 alias
标签信息放在一个容器中建立映射关系。在第三章中笔者简单介绍了关于 alias
标签的处理和 alias
标签的处理(别名注册) 在第三章中讲述的内容相对比较浅显, 在这一章中笔者将会详细的介绍别名注册。
alias
搭建过程一致,各位可以直接延用第三章的工程进行测试。搭建过环境的笔者可以不看本节,搭建的读者可以阅读本节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>
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)
这段代码进行分析。
从 getReaderContext().getRegistry().registerAlias(name, alias)
方法中我们可以确定方法的定义者是 : AliasRegistry
在 Spring 中关于 AliasRegistry
接口的实现类有很多,具体实现列表请看下图
这里对于大多数读者来说可能会迷茫,这么多实现类我应该去找哪一个看呢?笔者在这里给大家一个建议,在前文我们准备好了一个测试环境,同时我们也找到了需要分析的方法入口,打上断点单步调试就可以找到具体的方法实现了。
笔者在这里直接告诉各位读者真正的调用方法是由 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);
}
}
}
先来看参数
name
: 这个参数是 alias
标签中属性 name
的数据值, 一般情况下笔者将其称之为真名、BeanNamealias
: 这个参数是 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
),将 registeredName
和 name
做比较确认是否是相等的。递归也就是递归这一段内容了, 这就是验证别名的完整过程。
到此我们对于别名注册的方法已经了解了. 下面提出一个疑问: 当我们在使用别名去获取 Bean 对象的时候 Spring 是如何找到真正的名字的? 这就是我们下面要进行分析的问题. 各位读者可以思考一下翻一下代码。
首先告诉大家下面这个问题的答案。
当我们在使用别名去获取 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
中得分两步看
那么我们就按照代码执行顺序先来看参数的处理: 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;
});
}
在这个方法中根据参数是否是 &
字符串开头进行两种处理
&
开头直接返回参数&
开头切割字符串后返回在这个方法中得到的都是一个字符串, 可能是别名, 可能是 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;
}
这就是一个简单的处理了. 直接从容器中获取对应的名称返回即可。
在本章节中笔者向各位介绍了别名注册和别名使用的源码分析,它们分别位于 AliasRegistry
、SimpleAliasRegistry
和 BeanFactoryUtils
之中。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。