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)


相关推荐

  • ProgressDialog 使用

    ProgressDialog 使用ProgressDialog[功能]ProgressDialog也是一种Dialog一般在出现ProgressDialog后台都会再开辟Thread来做一些耗时的工作我演示的是从100数到0这段时间既不太长不用浪费时间等待同时也能明显地看出效果[代码]1.ProgressDialog使用[code="java"]public…

  • flowable camunda activiti 功能对比

    flowable camunda activiti 功能对比flowablecamundaactiviti三个框架都是从jbpm框架诞生出来的,先是有jbpm4,然后出来了一个activiti5,activiti5发展一段时间,又出来了一个Camunda。activiti5发展了4年,紧接着出来了一个flowable。本文重点对flowablecamunda两个框架的功能对比。对比的camunda版本是7.10.0,flowable框架的版本…

  • C# SortedList类概念和示例

    SortedList类[C#]命名空间:System.Collections表示键/值对的集合,这些键和值按键排序并可按照键和索引访问。SortedList是Hashtable和A

    2021年12月21日
  • VC6的SDK:Microsoft Platform SDK February 2003下载地址「建议收藏」

    VC6的SDK:Microsoft Platform SDK February 2003下载地址「建议收藏」据说是最后一个支持VC6R的SDK,我在项目中出现如下错误:errorLNK2001:unresolvedexternalsymbol_CLSID_ConnectionManager明明包含了这些头文件,也链接了LIB。。。#include//启用、禁用网卡#pragmacomment(lib,”ole32.lib”)VC6使用WMI

  • T-SQL—理解CTEs

    T-SQL—理解CTEs

    2021年11月26日
  • 卡巴斯基的离线更新以及病毒库备份[通俗易懂]

    卡巴斯基的离线更新以及病毒库备份[通俗易懂]卡巴斯基的离线更新以及病毒库备份 1、如果你用的是卡巴斯基5.0…..(….为版本号),病毒库在X:\DocumentsandSettings\AllUsers\ApplicationData\KasperskyAnti-VirusPersonal\5.0\base(其中X为安装时操作系统盘符,下同。专业版为X:\DocumentsandSettings\AllUsers…

发表回复

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

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