微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

深入理解Spring别名机制

前言

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

image-20220621134756391

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

本文将基于 spring 源码 5.2.x 分支,围绕 SimpleAliasRegistry 解析 spring 的别名机制是如何实现的。

一、AliasRegistry

在 spring 的容器中,一个 bean 必须至少有一个名称,而一个名称可以有多个别名,别名亦可以有别名,若我们把这个最原始的名称称为 id,则结构可以有:

id -> id's alias1 -> alias of id's alias1 ... ...
   -> id's alias2 -> alias of id's alias2 ... ...

通过 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 {

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

}

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

1、注册别名

@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 {
            String registeredname = this.aliasMap.get(alias);
            // 2.获取该别名的对应的原名称,若该别名已有对应的原名称,则:
            if (registeredname != null) {
                // a.已对应的原名称和要对应的原名称相同,则放弃后续处理
                if (registeredname.equals(name)) {
                    // An existing alias - no need to re-register
                    return;
                }

                // a.若不允许重写原名称对应的别名,则直接抛出异常
                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);
}

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐