spring源码解析之-----SimpleAliasRegistry解析

一、介绍

AliasRegistry 是Spring 别名管理的的 接口, 而 SimpleAliasRegistry 是其实现类,代码也不是太多,就解读一下.
Spring 里面,如果是通过XML形式配置别名,一般有以下两种方法

  1. 通过 alias 标签配置
  2. 通过name 配置,多个别名之间用逗号隔开
//第一种,通过 alias 标签配置
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close" />
<alias name="dataSource" alias="readDataSource" />
<alias name="dataSource" alias="readDataSource2" />
//通过name 配置,多个别名之间用逗号隔开
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close" name="dataSource2,dataSource3"/>

如果是SpringBoot 项目,@Bean注解是可以实现的,看一下注解,发现 @Bean 里面的name 是数组形式,
是数组形式,如果配置多个名字,就是 主Bean名称 + 别名 ,如果没有配置,那就是方法名称

public @interface Bean {
	/**
	 * The name of this bean, or if several names, a primary bean name plus aliases.
	 * <p>If left unspecified, the name of the bean is the name of the annotated method.
	 * If specified, the method name is ignored.
	 * <p>The bean name and aliases may also be configured via the {@link #value}
	 * attribute if no other attributes are declared.
	 * @see #value
	 */
	@AliasFor("value")
	String[] name() default {};
    ................
}

例子如下:

@Configuration
public class ApplicationConfig {

    @Bean(value = {"bean1", "bean2", "bean3"})
    public String getBean() {
        return "the first bean";
    }

    @Bean(value = {"bean3", "bean4"})
    public String getBean1() {
        return "the second bean";
    }
}

在这里插入图片描述
这里 bean1是 bean 名称,bean2,bean3 ,bean4 都是别名, 需要特别注意的是 ,已经配置了 bean3 作为别名,但是 后面又定义了 bean3 ,并且指定别名bean4 ,在后续 获取 bean3 ,和 bean4 Bean 的时候,都是获取的bean1 对应的Bean ,也就是输出 结果为 the first bean

    public static void main(String[] args) {
        SpringApplication.run(ApplicationMain.class,args);
        String obj = BeanUtils.getBean("bean4",String.class);
        System.out.println(obj);

        String obj2 = BeanUtils.getBean("bean3",String.class);
        System.out.println(obj2);
    }
// 结果
the first bean
the first bean

这里的原因是由于在getBean 的时候,首先是进行的BeanName 别名转换,所以获取的就是aa 对应的Bean,getBean的源码解析 可查看 getBean 源码解析

二、源码解读

2.1 AliasRegistry

首先看一下 AliasRegistry 接口,管理别名的通用接口。 用作BeanDefinitionRegistry 的超级接口,里面就 4个方法,如下:


public interface AliasRegistry {
    // 增加,注册别名
	void registerAlias(String name, String alias);
    //删除,删除指定的别名
	void removeAlias(String alias);
    // 判断给定的名字,是否是定义的别名
	boolean isAlias(String name);
	 // 返回给定的name ,所定义的所有的别名
	String[] getAliases(String name);
}

2.2 SimpleAliasRegistry

我们主要分析 SimpleAliasRegistry 里面的对应的实现逻辑,首先定义了一个ConcurrentHashMap类型,用来存放 对应的 别名映射关系 , ConcurrentHashMap 是线程安全,对单个的put 是线程安全的. 这里的key 是 alias(别名),value 是Bean 的name

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

2.2.1 registerAlias

首先看一下registerAlias 的流程图:
在这里插入图片描述
源码解释如下:

@Override
	public void registerAlias(String name, String alias) {
	    // 判断 name 、alias 是否为空
		Assert.hasText(name, "'name' must not be empty");
		Assert.hasText(alias, "'alias' must not be empty");
		// 进行锁住
		synchronized (this.aliasMap) {
		    // 判断 alias 是否和 name 相等
			if (alias.equals(name)) {
			     // 如果相等,就从aliasMap 里面移除
				this.aliasMap.remove(alias);
				if (logger.isDebugEnabled()) {
					logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
				}
			}
			else {
			    // 从 aliasMap里面获取alias 对应的 value
				String registeredName = this.aliasMap.get(alias);
				if (registeredName != null) {
				     // value 值 和 name 相等,说明已经存在 ,不需要在注册,直接返回
					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);
				// 存入到 aliasMap 里面
				this.aliasMap.put(alias, name);
				if (logger.isTraceEnabled()) {
					logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'");
				}
			}
		}
	}

这里需要解释一下 ,为啥 aliasMap 已经是 ConcurrentHashMap 类型,ConcurrentHashMap 是线程安全的,为啥还是要加 synchronized ,主要是 ConcurrentHashMap 是单次操作 是线程安全的,这里 加上 synchronized 主要是为了防止多线程同时操作,比如 线程A 和线程B同时操作,线程A插入 A->B , 线程B插入 B->A ,如果不加 synchronized ,checkForAliasCircle 时都是会通过的,都是会成功的,其实是有问题的. 此外 ,这里 又有 remove、get、put 操作 ,多线程时也可能会出现异常
举个例子:
下面运行多次,结果不是 10000

    public static void main(String[] args) throws InterruptedException {
        ConcurrentHashMap<String,Integer> map = new ConcurrentHashMap<>();
        map.put("key",1);
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        for(int i=0;i<10000;i++){
            executorService.submit(()->{
               Integer value = map.get("key");
               map.put("key",value+1);
            });
        }
        Thread.sleep(5000);
        System.out.println("------" + map.get("key") + "------");
        executorService.shutdown();
    }
    // 结果:------8203------

2.2.2 resolveAliases

这个方法主要是对 aliasMap 里面的key 和value 的值进行解析,可能key 、value 里面有有一些占位符或者前置、后置之类的等需要进行替换处理之类,大致的逻辑 和上面registerAlias() 方法有一些类似,大致流程如下:

  1. 对aliasMap 进行 copy出一份 aliasCopy
  2. 对 aliasCopy 进行遍历 ,每次遍历对 alias 和 registeredName 进行解析
  3. 判断 如果解析出来 ,出现 key或者value 为空的情况,或者key 和value 相等的情况,就remove 掉
  4. 如果解析之后 的 resolvedAlias 和原先的 alias 不相等,继续判断 aliasMap 里面是否存在key 为 resolvedAlias 的值,如果不存在,校验是否循环指向,remove 解析之前的键值对, 加入解析之后的键值对;如果存在,判断对应的值是否和resolvedName 相等,相等直接remove 解析之前的键值对,不相等报错,提示已经存在。
  5. 如果解析之前 alias 等于解析之后的 resolvedAlias,只要替换一下 value 即可

这里第三个 else if 里面 ,直接 替换原先的值 为 解析后的resolvedName 值 ,这里为啥不校验 循环依赖? 没有调用 checkForAliasCircle(),是不是不严谨?

源码注解如下:

public void resolveAliases(StringValueResolver valueResolver) {
		Assert.notNull(valueResolver, "StringValueResolver must not be null");
		//进行锁住,里面有put、get、remove 等诸多操作
		synchronized (this.aliasMap) {
		    // 进行复制一份
			Map<String, String> aliasCopy = new HashMap<>(this.aliasMap);
			// 遍历循环
			aliasCopy.forEach((alias, registeredName) -> {
			    // 对key、value 进行解析处理
				String resolvedAlias = valueResolver.resolveStringValue(alias);
				String resolvedName = valueResolver.resolveStringValue(registeredName);
				// 如果解析出来 ,出现 key或者value  为空的情况,或者key 和value 相等的情况,就remove 掉
				if (resolvedAlias == null || resolvedName == null || resolvedAlias.equals(resolvedName)) {
					this.aliasMap.remove(alias);
				}
				//解析之后,resolvedAlias 不等于 alias
				else if (!resolvedAlias.equals(alias)) {
					String existingName = this.aliasMap.get(resolvedAlias);
					//aliasMap 里面存在resolvedAlias 对应的value 
					if (existingName != null) {
					    // 解析之后的resolvedAlias->resolvedName ,存在,直接把原先的 alias 删除即可
						if (existingName.equals(resolvedName)) {
							// Pointing to existing alias - just remove placeholder
							this.aliasMap.remove(alias);
							return;
						}
						//如果aliasMap里面存在resolvedAlias 对应的value ,但是和解析之后的resolvedName  不一样,就直接报错,提示已经注册过,这里和上面不一样,没有是否可以覆盖的逻辑
						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);
				}
				// 这里说明 解析之前 alias 等于解析之后的 resolvedAlias,只要替换一下 value 即可
				else if (!registeredName.equals(resolvedName)) {
					this.aliasMap.put(alias, resolvedName);
				}
			});
		}
	}

2.2.3 checkForAliasCircle

这个方法主要是检查是否存在 给定名称是否以别名的形式指向给定别名, 通俗一些就是 相互指向,如a -->b ,b–>a;
图1
图2
hasAlias 这里的主要逻辑如下:

  1. 遍历aliasMap 里面的key-value ,首先判断 value 是否和 给定的Bean name 相等
  2. 如果相等,再判断key 是否和 alias 相等,如果相等,说明存在环路 指定(图1)
  3. 如果不相等,继续 递归调用判断,可能存在图2这种多个循环指向
	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) {
		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;
	}

2.2.4 getAliases

这里就是 遍历aliasMap ,逐个判断 键值对 key -value 的 value 是否 和给定的name 是否相等,如果相等,先加入result, 再递归查询 是否存在 指向 key 的别名, 存在就 加入

	@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);
			}
		});
	}

2.2.5 canonicalName

canonicalName 方法就是获取注册的名字,例如存在 A->B ,B->C ,C->D ,输入A,最终获取到D

	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;
	}

三、总结

整体SimpleAliasRegistry 里面的思路清晰,主要是有一处上面提到的
resolveAliases 方法里面,第三个 else if 里面 ,直接 替换原先的值 为 解析后的resolvedName 值 ,这里为啥不校验 循环依赖? 没有调用 checkForAliasCircle(),是不是不严谨?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一直打铁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值