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(),是不是不嚴謹?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章