spring bean别名注解_java.lang.clonable是类

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

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

前言

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

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

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/170759.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 至强e5处理器天梯图_e系列cpu天梯图

    至强e5处理器天梯图_e系列cpu天梯图lintel的至强CPU(Xeon)是为服务器准备的,优点核心数、线程数超多,对多任务处理优势明显,现在很多桌面电脑也会搭配志强CPU,用于游戏挂机,多任务处理等等。那么你们知道至强CPU性能排行榜,志强CPU中哪个最强,感兴趣的朋友一起来看看至强系列cpu天梯图,由本站2020年6月发布。至强CPU单线程跑分和多线程跑分性能排行榜:至强系列cpu天梯图2020:(数据比较多,大家可以使用CTR…

  • LAMP安全配置「建议收藏」

    LAMP安全配置「建议收藏」1.设置mysql密码,删除多余root账号[root@localhost~]#mysql-uroot-pmysql>setpassword=password(“111″);mysql>usemysql;mysql>deletefrom`user`whereuser!=”root”;mysql>deletefrom`user`whereuser=”root”

  • 中国程序员的悲哀

    中国程序员的悲哀
    中国程序员有个很悲哀的地方,大多数程序都对微软崇拜有加,奉若神明;然而大多数人都用着盗版的微软操作系统,盗版的visualstudio,然后还牛逼哄哄的出个什么微软vs使用心得。在他们眼里软件本身并不是商品,软件衍生出来的服务才能赚钱。
     
    这就好比几个小偷偷了别人的手机,然后交流用什么方法销赃才能最赚钱,你会觉得小偷太无耻了。但是如果满大街都是小偷,那你就会习以为常了。这么一想,发觉中国的程序员是抛开道德观念的,一心研究技术的。
     
    但是这不能怪程序员

  • 微信小程序api

    微信小程序api1.api1.概述小程序开发框架提供丰富的微信原生API,可以方便的调起微信提供的能力,如获取用户信息,本地存储,支付功能等2.api分类监听api约定以on开头的API用了来监听某个事件是否触发同步api约定,以Sync结尾的API都是同步API,直接接取函数返回的的结果既可,不需要等待异步api大多数API都是异步API,如wx.request,wx.login等,异步api方法主体是object结构,都有success/faill/complete几乎所有的异步api都支

  • facets学习(1):什么是facets

    facets学习(1):什么是facetsML数据集可以包含数亿个数据点,每个数据点由数百(甚至数千)的特征组成,几乎不可能以直观的方式了解整个数据集。为帮助理解、分析和调试ML数据集,谷歌开源了Facets,一款可视化工具。Fac

  • 【Swift】学习笔记(一)——熟知 基础数据类型,编码风格,元组,主张

    【Swift】学习笔记(一)——熟知 基础数据类型,编码风格,元组,主张

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号