【小家Spring】分享Spring中一个小巧而优雅的类SimpleAliasRegistry源码分析(别名注册、管理器)

每篇一句

世界上只有两种东西有真正的价值,一种是创世人所未见,一种是对已见之事的推进与优化,做一个比现有的还烂的东西,本身是没价值的,只能当作练习使用,这句话的逻辑已经很明晰了,不接受反驳

前言

上一篇分享了:【小家Spring】一文读懂Spring中的BeanFactory和FactoryBean的区别。为了缓解疲劳,本文来个小插曲~~不用费太多脑力的

Spring是一个非常优秀且流行的框架,里面不乏有很多优秀的设计模式、设计思想。

本文主要针对其中一个非常小巧的类:SimpleAliasRegistry做一个源码解读。顺便也分享给大家,若有分析得不到位的地方,非常欢迎指正,毕竟我也是第一次看。

分析此类的源码是因为此类很具有代表性,可以部分代表Spring的代码功底,优雅~~~因为群里有好几次提到过说此类虽然很小巧,但是代码设计得很优雅

初识

首先看到,这个类实现了接口AliasRegistry,而这个接口顾名思义:它就是别名管理器。Spring提供了一个默认实现:SimpleAliasRegistry。内部会缓存这些别名和真实名称的对应关系

在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了啊,所以别名这个在Boot这就不会再用了。

其实不然,SpringBoot中我们配置一个Bean一般会这么来写:

@Configuration
public class ApplicationConfig {

    @Bean
    public Object object() {
        return new Object();
    }

}

这个时候我打断点,发现还真的没有注册别名。而@Bean注解里也并没有alias等相关属性,是不是Boot就真的不支持了呢?

其实,只支持的。@Bean虽然没有alias属性,但是它的名称可以是数组,可以写多个名称,而经过我实现发现。当只写一个值的时候,只有名称没有别名。但是当你写多个值的时候,除了第一个是名称,后面的全都是别名。

    @Bean(value = {"aaa", "bbb", "ccc"})
    public Object object() {
        return new Object();
    }

断点启动:
《【小家Spring】分享Spring中一个小巧而优雅的类SimpleAliasRegistry源码分析(别名注册、管理器)》
《【小家Spring】分享Spring中一个小巧而优雅的类SimpleAliasRegistry源码分析(别名注册、管理器)》
证实了我上面的结论。虽然别名我们用得挺少,特别是在当下的Boot环境了。但是强大的Spring还是支持的。

当然这并不是本文讨论的重点,重点还是看“优雅的”代码:

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行左右,但是里面的写法确实是有不少可圈可点的地方。

Tips:此类在Spring中都是被Bean定义、创建的时候继承使用,和Bean的定义相关联
《【小家Spring】分享Spring中一个小巧而优雅的类SimpleAliasRegistry源码分析(别名注册、管理器)》

为了便于理解,我这里把最重要的一个方法:registerAlias(String name, String alias)画出逻辑流程图如下:
《【小家Spring】分享Spring中一个小巧而优雅的类SimpleAliasRegistry源码分析(别名注册、管理器)》

源码步骤分析

首先,使用了一个线程安全的Map:ConcurrentHashMap当做注册表来缓存alias和name的对应关系。key为alias别名,value为name真实值。可以通过别名获取到真实值,进而获取到Bean实例

private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);

为了方便大家下面阅读源码更好的理解为什么这么做?我在这里先说明两点:
1、需要避免循环别名和name之间循环引用的问题。比如a->b b->c c->a这就循环引用了,是需要避免的,否则很容易出问题
2、不能出现并发问题直接出现如下情况。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别名呢?

注册的难点在于如何防止重现名称和别名之间的重复引用。

最后用一张图形象的解释一下,为什么需要加锁?

在注册别名时,检查别名是否注册过名称这一步,如果不对注册表加锁,会导致检查出现问题,最终导致出现重复引用
《【小家Spring】分享Spring中一个小巧而优雅的类SimpleAliasRegistry源码分析(别名注册、管理器)》
两个注册过程并发进行,在检查时两个注册过程均未发现问题,但是注册过后便会出现问题
《【小家Spring】分享Spring中一个小巧而优雅的类SimpleAliasRegistry源码分析(别名注册、管理器)》

知识交流

《【小家Spring】分享Spring中一个小巧而优雅的类SimpleAliasRegistry源码分析(别名注册、管理器)》
若群二维码失效,请加微信号(或者扫描下方二维码):fsx641385712。
并且备注:“java入群” 字样,会手动邀请入群

《【小家Spring】分享Spring中一个小巧而优雅的类SimpleAliasRegistry源码分析(别名注册、管理器)》

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/f641385712/article/details/85081323
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞