目錄
一、介紹
AliasRegistry 是Spring 別名管理的的 接口, 而 SimpleAliasRegistry 是其實現類,代碼也不是太多,就解讀一下.
Spring 裏面,如果是通過XML形式配置別名,一般有以下兩種方法
- 通過 alias 標籤配置
- 通過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() 方法有一些類似,大致流程如下:
- 對aliasMap 進行 copy出一份 aliasCopy
- 對 aliasCopy 進行遍歷 ,每次遍歷對 alias 和 registeredName 進行解析
- 判斷 如果解析出來 ,出現 key或者value 爲空的情況,或者key 和value 相等的情況,就remove 掉
- 如果解析之後 的 resolvedAlias 和原先的 alias 不相等,繼續判斷 aliasMap 裏面是否存在key 爲 resolvedAlias 的值,如果不存在,校驗是否循環指向,remove 解析之前的鍵值對, 加入解析之後的鍵值對;如果存在,判斷對應的值是否和resolvedName 相等,相等直接remove 解析之前的鍵值對,不相等報錯,提示已經存在。
- 如果解析之前 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;
hasAlias 這裏的主要邏輯如下:
- 遍歷aliasMap 裏面的key-value ,首先判斷 value 是否和 給定的Bean name 相等
- 如果相等,再判斷key 是否和 alias 相等,如果相等,說明存在環路 指定(圖1)
- 如果不相等,繼續 遞歸調用判斷,可能存在圖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(),是不是不嚴謹?