《Spring系列》第11章 别名机制

文章详细介绍了Spring容器中的别名注册机制,主要涉及AliasRegistry接口和SimpleAliasRegistry实现类。SimpleAliasRegistry通过内部Map存储别名与原名称的映射,支持别名注册、获取、移除以及处理循环引用。同时,文章提到了线程安全性和别名覆盖的策略。
摘要由CSDN通过智能技术生成

前言

在 spring 容器中,允许通过名称或别名来获取 bean ,这个能力来自于顶层接口 AliasRegistry,分析类下属的关系图,可以看到,几乎所有主要容器都直接或间接的实现了 AliasRegistry 接口。
在这里插入图片描述

AliasRegistry 的结构非常简单,主要的类就是 AliasRegistry 接口与他的实现类 SimpleAliasRegistry,后续的实现类基本都直接或间接的继承了 SimpleAliasRegistry

一、AliasRegistry

在 spring 的容器中,一个 bean 必须至少有一个名称,而一个名称可以有多个别名,别名亦可以有别名

通过 bean 的 id,或与该 id 直接、间接相关的别名,都可以从容器中获取到对应的 bean。

AliasRegistry 的作用就是定义这一套规则:

public interface AliasRegistry {
	
   void registerAlias(String name, String alias);

   void removeAlias(String alias);

   boolean isAlias(String name);

   String[] getAliases(String name);

}

二、SimpleAliasRegistry

SimpleAliasRegistry 是直接基于 AliasRegistry 接口提供的简单实现类,他在内部维护了一个 Map 集合,以别名作为 key,别名对应的原名称作为 value:

public class SimpleAliasRegistry implements AliasRegistry {

   /** Logger available to subclasses. */
   protected final Log logger = LogFactory.getLog(getClass());

   /** Map from alias to canonical name. */
   private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);
    
  
   // ...
}

SimpleAliasRegistry 中,我们在上文所提到的 id ,被称为标准名称 CanonicalName

1.注册别名

  1. 名称和别名相同则不注册
  2. 名称和已注册的名称相同,则不注册
  3. 如果别名不允许覆盖则抛出异常
  4. 存入映射关系
@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) {
      // 1.若别名与原名称一致,则直接移除该别名,否则继续后续处理
      if (alias.equals(name)) {
         this.aliasMap.remove(alias);
         if (logger.isDebugEnabled()) {
            logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
         }
      }
      else {
         // 2.获取该别名的原名称
         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 + "'.");
            }
            if (logger.isDebugEnabled()) {
               logger.debug("Overriding alias '" + alias + "' definition for registered name '" +
                     registeredName + "' with new target name '" + name + "'");
            }
         }
         // 检查是否存在循环引用
         checkForAliasCircle(name, alias);
         
         // 建立别名与原名称的映射关系
         this.aliasMap.put(alias, name);
         if (logger.isTraceEnabled()) {
            logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'");
         }
      }
   }
}

是否允许覆盖别名对应的原名称

registerAlias 方法中,调用了 allowAliasOverriding 方法,该方法在 SimpleAliasRegistry 中固定返回 true

protected boolean allowAliasOverriding() {
    return true;
}

该方法决定一个已经有对应原名称的别名,是否允许被重新被对应到另一个原名称。跟 HashMap 中的 afterNodeXXX 方法一样,这里很明显是留给子类重写的钩子方法。

检查是否存在循环引用

registerAlias 方法中,调用了 checkForAliasCircle 以检查别名是否存在 a -> b -> a 这样的循环引用:

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) {
   String registeredName = this.aliasMap.get(alias);
   // 判断该别名对应的原名称是否就是要找的原名称
   return ObjectUtils.nullSafeEquals(registeredName, name) ||
         // 递归检查别名的别名对应的原名称是否为要找的原名称
         (registeredName != null && hasAlias(name, registeredName));
}

因为在 SimpleAliasRegistry 中,一个名称作为另一个名称的别名后,该名称仍然允许有别的名称作为它的别名。

举个例子,实际场景中,可能会出现 a -> b -> c 这种情况,对应在 aliasMap 中则存在 b -> ac -> b 的引用关系。

此时,若想要确定 c 是否是 a 的别名,就需要在通过 c 取出 b 后,再向上递归根据 b 找到 a,把每一级的关系却判断一遍。

为什么要加锁

理解了循环引用的检查,也就不难理解加锁的必要性了。实际使用中,SimpleAliasRegistry 基本是作为单例使用,此时不可避免的可能会存在多线程调用 registerAlias 的可能性,即便 aliasMap 使用的 ConcurrentHashMap 本身是线程安全的,但是这不能阻止出现 a -> bb -> a 两个操作的同时发生,此时 ab 构成了循环引用,而每个线程中的 checkForAliasCircle 却未必能够正确的检测出问题。

2.获取名称

获取全部别名

@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);
        }
    });
}

获取别名也很好理解,首先加锁同样的为了避免删除的同时注册别名导致的一些问题,然后递归调用的 retrieveAliases 也是为了防止出现“别名还有别名”的情况,getAliases 最终将返回该名称与该名称构成直接或间接引用关系的别名。

获取标准名称

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;
}

该方法的作用是通过一个别名获得对应的标准名称。举个例子,假如现在存在如下别名引用关系:a -> b -> c -> d,则从 abcd 任意一个名称开始调用该方法,则最终都会返回 a

3.移除别名

@Override
public void removeAlias(String alias) {
    synchronized (this.aliasMap) {
        String name = this.aliasMap.remove(alias);
        if (name == null) {
            throw new IllegalStateException("No alias '" + alias + "' registered");
        }
    }
}

4.名称转换

有时候,直接注册到 SimpleAliasRegistry 的名称和对应别名未必就是最想要,还需要替换占位符或者进行一些大小写转换的操作,这就需要通过 resolveAliases 方法来完:

public void resolveAliases(StringValueResolver valueResolver) {
    Assert.notNull(valueResolver, "StringValueResolver must not be null");
    synchronized (this.aliasMap) {
        Map<String, 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);
                // a.若转换后的别名已有对应的原名称,且与转换后的新原名称相同,则移除该别名的映射关系,否则报错
                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);
            }
        });
    }
}

其中,StringValueResolver 是一个函数式接口,它用于将一个字符串转为另一个字符串,也就是 resolve 的过程:

@FunctionalInterface
public interface StringValueResolver {
	@Nullable
	String resolveStringValue(String strVal);
}

edName);
}
});
}
}


其中,`StringValueResolver` 是一个函数式接口,它用于将一个字符串转为另一个字符串,也就是 `resolve` 的过程:

```java
@FunctionalInterface
public interface StringValueResolver {
	@Nullable
	String resolveStringValue(String strVal);
}

resolveAliases 的过程基本与注册别名的方法 registerAlias 一致,本质上就是将原有的别名的与名称转换后再次建立映射关系,然后移除旧的映射关系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

为人师表好少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值