Spring-AliasRegistry

使用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"/>

使用 @Bean 註解的時候

@Bean(value = {"aaa", "bbb", "ccc"})

那麼 除了第一個是這個 beanName(bean id) 之外、其他的都是 alias

public interface AliasRegistry {
   /**
    * 爲這個 name 註冊一個 alias
    */
   void registerAlias(String name, String alias);
   /**
    * 從註冊表中移除這個alias對應的關係
   */
   void removeAlias(String alias);
   /**
    * 給定的這個 name是否是一個 別名
    */
   boolean isAlias(String name);
   /**
    * 根據這個 bean name 獲取所有他的別名
    */
   String[] getAliases(String name);
}

AliasRegistry 其中的一個實現類 SimpleAliasRegistry

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

使用 Map 來存儲、key 爲 alias 、value 爲 beanName (並不是意味着這個value只能是在spring 容器中存在的bean 的 id、也可以是一個 alias、比如說我對象A有個別名是小A、那麼這個小A同樣可以有它的別名小AA、那麼Map的情況就是[(小A,對象A的id/beanName),(小AA,小A)] )

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

上面的方法實現相對而已是比較簡單的

@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)) {
          // 這一步是有必要這麼做的、如果這個 alias 已經被其他的 bean 所使用、
          // 那麼我這個算是最新的了 
          // 至於爲啥不放在 map 裏面、因爲key 和 value 一樣的話、
          // 後面的 getAliases 會死循環
         this.aliasMap.remove(alias);
      } else {
         // 根據這個 alias 找出是否已經註冊的
         String registeredName = this.aliasMap.get(alias);
         // 已經有人註冊過這個 alias了
         if (registeredName != null) {
            // 那麼巧、bean Name 是一樣的、
            if (registeredName.equals(name)) {
               // An existing alias - no need to re-register
               return;
            }
            // 是否允許 alias覆蓋、默認是允許的
            if (!allowAliasOverriding()) {
               throw new IllegalStateException("xxx 已省略顯示");
            }
            
         }
         // 檢查是否循環依賴
         checkForAliasCircle(name, alias);
         this.aliasMap.put(alias, name);
         
      }
   }
}

關於 alias的循環註冊

protected void checkForAliasCircle(String name, String alias) {
   // 我們要註冊的是 name 擁有別名 alias
   // 那麼我們就要判斷是否 有 alias 擁有別名 name 、
   // 如果有的話、那麼就是循環依賴了
   if (hasAlias(alias, name)) {
      throw new IllegalStateException("省略....");
   }
}

假如我們已經有了 test擁有別名testAlias01 的關係、那麼我們現在想要註冊 testAlias01 擁有 別名test

這個關係、那麼就檢查、看看再已用的關係中是否已經有 test擁有別名testAlias01 關係、如果有則是 別名的循環依賴

A->B->C->D->A

這種也是循環

Spring 源碼對應的單元測試

class SimpleAliasRegistryTests {

   @Test
   void aliasChaining() {
      SimpleAliasRegistry registry = new SimpleAliasRegistry();
      registry.registerAlias("test", "testAlias");
      registry.registerAlias("testAlias", "testAlias2");
      registry.registerAlias("testAlias2", "testAlias3");

      assertThat(registry.hasAlias("test", "testAlias")).isTrue();
      assertThat(registry.hasAlias("test", "testAlias2")).isTrue();
      assertThat(registry.hasAlias("test", "testAlias3")).isTrue();
      assertThat(registry.canonicalName("testAlias")).isEqualTo("test");
      assertThat(registry.canonicalName("testAlias2")).isEqualTo("test");
      assertThat(registry.canonicalName("testAlias3")).isEqualTo("test");
   }

   @Test  // SPR-17191
   void aliasChainingWithMultipleAliases() {
      SimpleAliasRegistry registry = new SimpleAliasRegistry();
      registry.registerAlias("name", "alias_a");
      registry.registerAlias("name", "alias_b");
      assertThat(registry.hasAlias("name", "alias_a")).isTrue();
      assertThat(registry.hasAlias("name", "alias_b")).isTrue();

      registry.registerAlias("real_name", "name");
      assertThat(registry.hasAlias("real_name", "name")).isTrue();
      assertThat(registry.hasAlias("real_name", "alias_a")).isTrue();
      assertThat(registry.hasAlias("real_name", "alias_b")).isTrue();

      registry.registerAlias("name", "alias_c");
      assertThat(registry.hasAlias("real_name", "name")).isTrue();
      assertThat(registry.hasAlias("real_name", "alias_a")).isTrue();
      assertThat(registry.hasAlias("real_name", "alias_b")).isTrue();
      assertThat(registry.hasAlias("real_name", "alias_c")).isTrue();
   }
}

已經使用了 ConcurrentHashMap爲啥還要使用 synchronized

在註冊別名時,檢查別名是否註冊過名稱這一步,如果不對註冊表加鎖,會導致檢查出現問題,最終導致出現重複引用

兩個註冊過程併發進行,在檢查時兩個註冊過程均未發現問題,但是註冊過後便會出現問題

https://blog.csdn.net/f641385712/article/details/85081323

img

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