【走近Spring】Spring中一个小巧而优雅的类SimpleAliasRegistry源码分析(别名注册、管理器)
Spring是一个非常优秀且流行的框架,里面不乏有很多优秀的设计模式、设计思想。
SimpleAliasRegistry这个类实现了接口AliasRegistry,而这个接口顾名思义:它就是别名管理器。SimpleAliasRegistry就是Spring提供的一个默认实现,其内部会缓存这些别名和真实名称的对应关系。
在Spring环境下,我们很容易的为一个Bean定义一个或者多个别名:
<bean id="app:dataSource" class="...">
<alias name="app:dataSoure" alias="user:dataSoure"/>
<alias name="app:dataSoure" alias="device:dataSoure"/>
</bean>
或者:
直接使用bean标签的name属性,就是别名
<bean id="aaa",name="bbb,ccc,ddd"/>
可能很多人就会问了,当前都SpringBoot环境了,哪还会用到xml了啊,所以别名这个在SpringBoot这就不会再用了。
其实不然,SpringBoot中我们配置一个Bean一般会这么来写:
@Configuration
public class ApplicationConfig {
@Bean
public Object object() {
return new Object();
}
}
这个时候我打断点,发现还真的没有注册别名。而@Bean注解里也并没有alias等相关属性,是不是SpringBoot就真的不支持了呢?
其实,是支持的。@Bean虽然没有alias属性,但是它的名称可以是数组,可以写多个名称,而经过我实验发现。当只写一个值的时候,只有名称没有别名。但是当你写多个值的时候,除了第一个是名称,后面的全都是别名。
@Bean(value = {"aaa", "bbb", "ccc"})
public Object object() {
return new Object();
}
当然这并不是本文讨论的重点,重点还是看“优雅的”代码:
public interface AliasRegistry {
//增 给name新增一个别名alias
void registerAlias(String name, String alias);
//删 删除一个别名
void removeAlias(String alias);
//此name是否含有别名
boolean isAlias(String name);
//获取此name对应的所有的别名
String[] getAliases(String name);
}
它的实现类有不少,此处我们只看SimpleAliasRegistry :
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);
@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) {
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);
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 + "'");
}
}
}
}
/**
* Return whether alias overriding is allowed.
* Default is {@code true}.
*/
protected boolean allowAliasOverriding() {
return true;
}
/**
* Determine whether the given name has the given alias registered.
* @param name the name to check
* @param alias the alias to look for
* @since 4.2.1
*/
public boolean hasAlias(String name, String alias) {
for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) {
String registeredName = entry.getValue();
if (registeredName.equals(name)) {
String registeredAlias = entry.getKey();
if (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias)) {
return true;
}
}
}
return false;
}
@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");
}
}
}
@Override
public boolean isAlias(String name) {
return this.aliasMap.containsKey(name);
}
@Override
public String[] getAliases(String name) {
List<String> result = new ArrayList<>();
synchronized (this.aliasMap) {
retrieveAliases(name, result);
}
return StringUtils.toStringArray(result);
}
/**
* Transitively retrieve all aliases for the given name.
* @param name the target name to find aliases for
* @param result the resulting aliases list
*/
private void retrieveAliases(String name, List<String> result) {
this.aliasMap.forEach((alias, registeredName) -> {
if (registeredName.equals(name)) {
result.add(alias);
retrieveAliases(alias, result);
}
});
}
/**
* Resolve all alias target names and aliases registered in this
* factory, applying the given StringValueResolver to them.
* <p>The value resolver may for example resolve placeholders
* in target bean names and even in alias names.
* @param valueResolver the StringValueResolver to apply
*/
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);
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);
}
});
}
}
/**
* Check whether the given name points back to the given alias as an alias
* in the other direction already, catching a circular reference upfront
* and throwing a corresponding IllegalStateException.
* @param name the candidate name
* @param alias the candidate alias
* @see #registerAlias
* @see #hasAlias
*/
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");
}
}
/**
* Determine the raw name, resolving aliases to canonical names.
* @param name the user-specified name
* @return the transformed name
*/
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;
}
}
本文就是重点分析此类,代码行数若除去注释,100行左右,但是里面的写法确实是有不少可圈可点的地方。
此类在Spring中都是被Bean定义、创建的时候继承使用,和Bean的定义相关联。
为了便于理解,我这里把最重要的一个方法:registerAlias(String name, String alias)画出逻辑流程图如下:
源码步骤分析:
首先,使用了一个线程安全的Map:ConcurrentHashMap当做注册表来缓存alias和name的对应关系。key为alias别名,value为name真实值。可以通过别名获取到真实值,进而获取到Bean实例。
private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);
为了方便大家下面阅读源码更好的理解为什么这么做?我在这里先说明两点:
- 需要避免循环别名和name之间循环引用的问题。比如a->b b->c c->a这就循环引用了,是需要避免的,否则很容易出问题
- 不能出现并发问题直接出现如下情况。a -> b b->a (其实也是一种循环引用嘛)。
首先我们看一下registerAlias方法,也是最为复杂点的方法:
@Override
public void registerAlias(String name, String alias) {
Assert.hasText(name, "'name' must not be empty");
Assert.hasText(alias, "'alias' must not be empty");
//此处注意:很多人疑问的地方,用了ConcurrentHashMap,为何此处还要加锁呢?有必要吗?
//答:非常有必要的。因为ConcurrentHashMap只能保证单个put、remove方法的原子性。而不能保证多个操作同时的原子性。比如我一边添加、一边删除 显然这是不被允许的
synchronized (this.aliasMap) {
//若发现别名和name是相同的,就不需要做啥了。而且顺手把这个key给移除掉
if (alias.equals(name)) {
this.aliasMap.remove(alias);
if (logger.isDebugEnabled()) {
logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
}
} else {
//拿到这个别名对应的name,看看该别名是否已经存在对应的name了
String registeredName = this.aliasMap.get(alias);
if (registeredName != null) {
//若已经存在对应的name了,而且还和传进俩的name相同,那啥都不做就行
if (registeredName.equals(name)) {
return;
}
//若存在对应的name了,切还不让复写此别名(让其指向别的name),那就跑错吧
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 + "'");
}
}
}
}
当alias对应的name不存在的时候,还需要去检查是否可能存在循环引用现象:checkForAliasCirclee如下:
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");
}
}
对应的hasAlias方法如下:
public boolean hasAlias(String name, String alias) {
for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) {
String registeredName = entry.getValue();
//若找到了此name 然后就拿出其对应的alias(可能会有多次哦)
if (registeredName.equals(name)) {
String registeredAlias = entry.getKey();
//如果此alias和传入的alias相同,返回true 证明name有这个alias
//一般人可能上面那一步就算了直接return了,但是,但是,但是还有一种情况也必须考虑到:倘若这个已经注册过的registeredAlias和传入的alias不相等。
//但是把他作为name去找是否有alias的时候,如果有也得判断是true,表示有。 防止了a -> b b->c c->a的循环的情况 此处处理可以说是非常的优雅和谨慎了~
if (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias)) {
return true;
}
}
}
return false;
}
该方法旨在判断:给定的name,是否已经有了对应的alias别名呢?
注册的难点在于如何防止重现名称和别名之间的重复引用。
最后用一张图形象的解释一下,为什么需要加锁?
在注册别名时,检查别名是否注册过名称这一步,如果不对注册表加锁,会导致检查出现问题,最终导致出现重复引用。
两个注册过程并发进行,在检查时两个注册过程均未发现问题,但是注册过后便会出现问题。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!