ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

深入理解Spring别名机制

2022-08-14 16:03:22  阅读:145  来源: 互联网

标签:String Spring 别名 alias 深入 名称 aliasMap name


前言

在 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 一致,本质上就是将原有的别名的与名称转换后再次建立映射关系,然后移除旧的映射关系。

标签:String,Spring,别名,alias,深入,名称,aliasMap,name
来源: https://www.cnblogs.com/Createsequence/p/16585541.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有