目录
一、介绍
AliasRegistry 是Spring 别名管理的的 接口, 而 SimpleAliasRegistry 是其实现类,代码也不是太多,就解读一下.
Spring 里面,如果是通过XML形式配置别名,一般有以下两种方法
- 通过 alias 标签配置
- 通过name 配置,多个别名之间用逗号隔开
//第一种,通过 alias 标签配置
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close" />
<alias name="dataSource" alias="readDataSource" />
<alias name="dataSource" alias="readDataSource2" />
//通过name 配置,多个别名之间用逗号隔开
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close" name="dataSource2,dataSource3"/>
如果是SpringBoot 项目,@Bean注解是可以实现的,看一下注解,发现 @Bean 里面的name 是数组形式,
是数组形式,如果配置多个名字,就是 主Bean名称 + 别名 ,如果没有配置,那就是方法名称
public @interface Bean {
/**
* The name of this bean, or if several names, a primary bean name plus aliases.
* <p>If left unspecified, the name of the bean is the name of the annotated method.
* If specified, the method name is ignored.
* <p>The bean name and aliases may also be configured via the {@link #value}
* attribute if no other attributes are declared.
* @see #value
*/
@AliasFor("value")
String[] name() default {};
................
}
例子如下:
@Configuration
public class ApplicationConfig {
@Bean(value = {"bean1", "bean2", "bean3"})
public String getBean() {
return "the first bean";
}
@Bean(value = {"bean3", "bean4"})
public String getBean1() {
return "the second bean";
}
}
这里 bean1是 bean 名称,bean2,bean3 ,bean4 都是别名, 需要特别注意的是 ,已经配置了 bean3 作为别名,但是 后面又定义了 bean3 ,并且指定别名bean4 ,在后续 获取 bean3 ,和 bean4 Bean 的时候,都是获取的bean1 对应的Bean ,也就是输出 结果为 the first bean
public static void main(String[] args) {
SpringApplication.run(ApplicationMain.class,args);
String obj = BeanUtils.getBean("bean4",String.class);
System.out.println(obj);
String obj2 = BeanUtils.getBean("bean3",String.class);
System.out.println(obj2);
}
// 结果
the first bean
the first bean
这里的原因是由于在getBean 的时候,首先是进行的BeanName 别名转换,所以获取的就是aa 对应的Bean,getBean的源码解析 可查看 getBean 源码解析
二、源码解读
2.1 AliasRegistry
首先看一下 AliasRegistry 接口,管理别名的通用接口。 用作BeanDefinitionRegistry 的超级接口,里面就 4个方法,如下:
public interface AliasRegistry {
// 增加,注册别名
void registerAlias(String name, String alias);
//删除,删除指定的别名
void removeAlias(String alias);
// 判断给定的名字,是否是定义的别名
boolean isAlias(String name);
// 返回给定的name ,所定义的所有的别名
String[] getAliases(String name);
}
2.2 SimpleAliasRegistry
我们主要分析 SimpleAliasRegistry 里面的对应的实现逻辑,首先定义了一个ConcurrentHashMap类型,用来存放 对应的 别名映射关系 , ConcurrentHashMap 是线程安全,对单个的put 是线程安全的. 这里的key 是 alias(别名),value 是Bean 的name
private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);
2.2.1 registerAlias
首先看一下registerAlias 的流程图:
源码解释如下:
@Override
public void registerAlias(String name, String alias) {
// 判断 name 、alias 是否为空
Assert.hasText(name, "'name' must not be empty");
Assert.hasText(alias, "'alias' must not be empty");
// 进行锁住
synchronized (this.aliasMap) {
// 判断 alias 是否和 name 相等
if (alias.equals(name)) {
// 如果相等,就从aliasMap 里面移除
this.aliasMap.remove(alias);
if (logger.isDebugEnabled()) {
logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
}
}
else {
// 从 aliasMap里面获取alias 对应的 value
String registeredName = this.aliasMap.get(alias);
if (registeredName != null) {
// value 值 和 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);
// 存入到 aliasMap 里面
this.aliasMap.put(alias, name);
if (logger.isTraceEnabled()) {
logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'");
}
}
}
}
这里需要解释一下 ,为啥 aliasMap 已经是 ConcurrentHashMap 类型,ConcurrentHashMap 是线程安全的,为啥还是要加 synchronized ,主要是 ConcurrentHashMap 是单次操作 是线程安全的,这里 加上 synchronized 主要是为了防止多线程同时操作,比如 线程A 和线程B同时操作,线程A插入 A->B , 线程B插入 B->A ,如果不加 synchronized ,checkForAliasCircle 时都是会通过的,都是会成功的,其实是有问题的. 此外 ,这里 又有 remove、get、put 操作 ,多线程时也可能会出现异常
举个例子:
下面运行多次,结果不是 10000
public static void main(String[] args) throws InterruptedException {
ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>();
map.put("key",1);
ExecutorService executorService = Executors.newFixedThreadPool(100);
for(int i=0;i<10000;i++){
executorService.submit(()->{
Integer value = map.get("key");
map.put("key",value+1);
});
}
Thread.sleep(5000);
System.out.println("------" + map.get("key") + "------");
executorService.shutdown();
}
// 结果:------8203------
2.2.2 resolveAliases
这个方法主要是对 aliasMap 里面的key 和value 的值进行解析,可能key 、value 里面有有一些占位符或者前置、后置之类的等需要进行替换处理之类,大致的逻辑 和上面registerAlias() 方法有一些类似,大致流程如下:
- 对aliasMap 进行 copy出一份 aliasCopy
- 对 aliasCopy 进行遍历 ,每次遍历对 alias 和 registeredName 进行解析
- 判断 如果解析出来 ,出现 key或者value 为空的情况,或者key 和value 相等的情况,就remove 掉
- 如果解析之后 的 resolvedAlias 和原先的 alias 不相等,继续判断 aliasMap 里面是否存在key 为 resolvedAlias 的值,如果不存在,校验是否循环指向,remove 解析之前的键值对, 加入解析之后的键值对;如果存在,判断对应的值是否和resolvedName 相等,相等直接remove 解析之前的键值对,不相等报错,提示已经存在。
- 如果解析之前 alias 等于解析之后的 resolvedAlias,只要替换一下 value 即可
这里第三个 else if 里面 ,直接 替换原先的值 为 解析后的resolvedName 值 ,这里为啥不校验 循环依赖? 没有调用 checkForAliasCircle(),是不是不严谨?
源码注解如下:
public void resolveAliases(StringValueResolver valueResolver) {
Assert.notNull(valueResolver, "StringValueResolver must not be null");
//进行锁住,里面有put、get、remove 等诸多操作
synchronized (this.aliasMap) {
// 进行复制一份
Map<String, String> aliasCopy = new HashMap<>(this.aliasMap);
// 遍历循环
aliasCopy.forEach((alias, registeredName) -> {
// 对key、value 进行解析处理
String resolvedAlias = valueResolver.resolveStringValue(alias);
String resolvedName = valueResolver.resolveStringValue(registeredName);
// 如果解析出来 ,出现 key或者value 为空的情况,或者key 和value 相等的情况,就remove 掉
if (resolvedAlias == null || resolvedName == null || resolvedAlias.equals(resolvedName)) {
this.aliasMap.remove(alias);
}
//解析之后,resolvedAlias 不等于 alias
else if (!resolvedAlias.equals(alias)) {
String existingName = this.aliasMap.get(resolvedAlias);
//aliasMap 里面存在resolvedAlias 对应的value
if (existingName != null) {
// 解析之后的resolvedAlias->resolvedName ,存在,直接把原先的 alias 删除即可
if (existingName.equals(resolvedName)) {
// Pointing to existing alias - just remove placeholder
this.aliasMap.remove(alias);
return;
}
//如果aliasMap里面存在resolvedAlias 对应的value ,但是和解析之后的resolvedName 不一样,就直接报错,提示已经注册过,这里和上面不一样,没有是否可以覆盖的逻辑
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);
}
// 这里说明 解析之前 alias 等于解析之后的 resolvedAlias,只要替换一下 value 即可
else if (!registeredName.equals(resolvedName)) {
this.aliasMap.put(alias, resolvedName);
}
});
}
}
2.2.3 checkForAliasCircle
这个方法主要是检查是否存在 给定名称是否以别名的形式指向给定别名, 通俗一些就是 相互指向,如a -->b ,b–>a;
hasAlias 这里的主要逻辑如下:
- 遍历aliasMap 里面的key-value ,首先判断 value 是否和 给定的Bean name 相等
- 如果相等,再判断key 是否和 alias 相等,如果相等,说明存在环路 指定(图1)
- 如果不相等,继续 递归调用判断,可能存在图2这种多个循环指向
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");
}
}
public boolean hasAlias(String name, String alias) {
for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) {
String registeredName = entry.getValue();
if (registeredName.equals(name)) {
String registeredAlias = entry.getKey();
if (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias)) {
return true;
}
}
}
return false;
}
2.2.4 getAliases
这里就是 遍历aliasMap ,逐个判断 键值对 key -value 的 value 是否 和给定的name 是否相等,如果相等,先加入result, 再递归查询 是否存在 指向 key 的别名, 存在就 加入
@Override
public String[] getAliases(String name) {
List<String> result = new ArrayList<>();
synchronized (this.aliasMap) {
retrieveAliases(name, result);
}
return StringUtils.toStringArray(result);
}
private void retrieveAliases(String name, List<String> result) {
this.aliasMap.forEach((alias, registeredName) -> {
if (registeredName.equals(name)) {
result.add(alias);
retrieveAliases(alias, result);
}
});
}
2.2.5 canonicalName
canonicalName 方法就是获取注册的名字,例如存在 A->B ,B->C ,C->D ,输入A,最终获取到D
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;
}
三、总结
整体SimpleAliasRegistry 里面的思路清晰,主要是有一处上面提到的
resolveAliases 方法里面,第三个 else if 里面 ,直接 替换原先的值 为 解析后的resolvedName 值 ,这里为啥不校验 循环依赖? 没有调用 checkForAliasCircle(),是不是不严谨?