15-SimpleAliasRegistry

背景簡介

出現(xiàn)的原因

我們在上面介紹了AliasRegistry定義了從 alias-->id的種種增刪改查操作库继。在 Spring 框架的運(yùn)行中使用場景往往是從配置文件或者打著注解的代碼中加載拼坎、解析成 BDH(BeanDefinitionHolder)然后注冊纠亚,所以我們對AliasRegistry的實(shí)現(xiàn)也應(yīng)該是內(nèi)存數(shù)據(jù)結(jié)構(gòu)——我們本節(jié)介紹的SimpleAliasRegistry就是使用Java的集合類對AliasRegistry做了基本實(shí)現(xiàn)驱敲。

職責(zé)

SimpleAliasRegistry為他的子類提供了線程安全的管理別名映射的功能。

注意點(diǎn)

這個(gè)類使用了一些線程安全機(jī)制繁扎,根據(jù)之前學(xué)習(xí)的多線程知識進(jìn)行甄別悠垛。

源碼

實(shí)例屬性

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

使用 ConcurrentHashMap以保證多線程操作存取時(shí)的線程安全但荤。

從這里看是將線程安全委托給ConcurrentHashMap了。

注意:這個(gè) Mapkey是別名彼城,value是id或者另一個(gè)別名【這個(gè)可以傳遞的】

實(shí)現(xiàn)接口方法【增诅蝶、刪退个、查】

@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) {
        // 如果別名就是真實(shí)名稱,直接去掉緩存即可调炬,沒必要通過 SimpleAliasRegistry來走彎路
        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) {//別名已經(jīng)占位置了
                if (registeredName.equals(name)) {
                    // 已經(jīng)存在了语盈,不用再加了
                    return;
                }
                if (!allowAliasOverriding()) { // 看是否允許別名覆蓋
                    throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +
                            name + "': It is already registered for name '" + registeredName + "'.");
                }
                if (logger.isInfoEnabled()) {
                    logger.info("Overriding alias '" + alias + "' definition for registered name '" +
                            registeredName + "' with new target name '" + name + "'");
                }
            }
      // 到這里,有兩種情況:
      // 或者這個(gè) alias 沒在 map 中當(dāng) key 
      // 或者 alias 存在筐眷,但是 value 和入?yún)⒉煌铱筛采w 【當(dāng)然黎烈,不確定是不是間接的】 
      //
      // 因?yàn)檫@里是遞歸的,防止出現(xiàn)循環(huán)依賴 
            checkForAliasCircle(name, alias); // 見工具函數(shù)
            this.aliasMap.put(alias, name);
            if (logger.isDebugEnabled()) {
                logger.debug("Alias definition '" + alias + "' registered for name '" + name + "'");
            }
        }
    }
}

protected boolean allowAliasOverriding() {
    return true;
}

這里匀谣,你會發(fā)現(xiàn):整個(gè)操作都是加鎖的照棋,因?yàn)檫壿嫳容^復(fù)雜,設(shè)計(jì)多次的存取判斷武翎,僅靠委托給ConcurrentHashMap是不行的烈炭。

既然這樣,如果我們使用了鎖來操作宝恶,是不是ConcurrentHashMap又是多余的了呢符隙?

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

刪除操作也進(jìn)行了加鎖。ConcurrentHashMap照樣沒用垫毙。

不允許隨便傳值霹疫,刪除不存在的key會報(bào)錯(cuò)

@Override
// 判斷入?yún)e名是否存在
public boolean isAlias(String name) {
    return this.aliasMap.containsKey(name);
}

@Override
// 獲得直接/間接指向 name 的 alias
public String[] getAliases(String name) {
    List<String> result = new ArrayList<>();
    synchronized (this.aliasMap) {
        retrieveAliases(name, result);// 
    }
    return StringUtils.toStringArray(result);
}

// 思路很簡單,因?yàn)槭歉鶕?jù) value 找 key 综芥,所以需要遍歷 Map丽蝎。
// 遞歸操作,涉及樹遍歷【深度遍歷】
private void retrieveAliases(String name, List<String> result) {
  this.aliasMap.forEach((alias, registeredName) -> {
    if (registeredName.equals(name)) {
      result.add(alias);
      retrieveAliases(alias, result);
    }
  });
}

工具函數(shù)

/**
 * 對 Map 中的所有 key膀藐、value進(jìn)行解析操作屠阻,因?yàn)橛械睦锩媸怯?Spring 表達(dá)式的,需要去掉 Spring 的
 * 那些占位符
 */
public void resolveAliases(StringValueResolver valueResolver) {
    Assert.notNull(valueResolver, "StringValueResolver must not be null");
    synchronized (this.aliasMap) {
    // 復(fù)制 Map额各,方便找一個(gè)不動(dòng)的參考來進(jìn)行遍歷
        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)) {
                // 解析后的key value存在空国觉,或者解析后key value相同
        // 也就是說這個(gè)映射是多余的。直接刪除
                this.aliasMap.remove(alias);
            } else if (!resolvedAlias.equals(alias)) {
        // 完成解析虾啦,且key麻诀、value看起來合理
                String existingName = this.aliasMap.get(resolvedAlias);
                // 解析后的 key 發(fā)現(xiàn)已經(jīng)在map中存在了
                if (existingName != null) {
                    if (existingName.equals(resolvedName)) {
                        // 解析后的 value 和解析后的 key 映射的是一個(gè)值,所以不用專門把解析后的key value放進(jìn)去了
                        // 直接刪了未解析的映射即可
                        // Pointing to existing alias - just remove placeholder
                        this.aliasMap.remove(alias);
                        return;
                    }
                    // 解析后的 key 已經(jīng)存在傲醉,但是指向的 value 不是我們解析出來的 value 蝇闭,這個(gè)坑已經(jīng)被占了,存在沖突需频!
                    throw new IllegalStateException(
                            "Cannot register resolved alias '" + resolvedAlias + "' (original: '" + alias +
                                    "') for name '" + resolvedName + "': It is already registered for name '" +
                                    registeredName + "'.");
                }
                // 解析后的 key 在 map 中不存在丁眼,看看沒有啥循環(huán)引用啥的,就把未解析的鍵值對換成解析后的鍵值對即可
                checkForAliasCircle(resolvedName, resolvedAlias);
                this.aliasMap.remove(alias);
                this.aliasMap.put(resolvedAlias, resolvedName);
            } else if (!registeredName.equals(resolvedName)) {
                // 解析后的 key 不變昭殉,但是 value 變了苞七,直接做值覆寫即可
                // TODO:key 不變 value 變了 不用 check 一下循環(huán)引用嗎藐守?
                this.aliasMap.put(alias, resolvedName);
            }
        });
    }
}

/**
 * 檢測循環(huán)依賴,即存在 A--->B--->*--->A
 *
 * @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");
    }
}

/**
 * 判斷是否存在 alias--->name 的直接或者間接的映射
 **/ 
public boolean hasAlias(String name, String alias) { // 存在多線程訪問時(shí)的問題
    // 遞歸函數(shù)蹂风,從真實(shí)名稱往上走卢厂。
    // TODO: 這里存疑,為什么不能直接運(yùn)用map的特點(diǎn)惠啄,從alias開始找慎恒,這樣大大降低了復(fù)雜度,還可以用循環(huán)代替遞歸
    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;
}

/**
 * 找到別名的最終映射的 id
 */
// 通過從 map 中反復(fù)查找撵渡,找到 "name" 對應(yīng)的 alias 在 map 中最終指向的名字
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;
}

總結(jié)記錄

就是一個(gè)基本的實(shí)現(xiàn)融柬。重點(diǎn)熟悉一下那幾個(gè)工具函數(shù)即可。

問題

濫用線程安全類

使用ConcurrentHashMap在操作中沒有任何幫助趋距,原因如下:

  1. 實(shí)例屬性為private且沒有getter粒氧,不存在泄漏可能
  2. 所有方法均為復(fù)雜操作,需要自行加鎖节腐,無法直接委托給ConcurrentHashMap

綜上外盯,使用ConcurrentHashMap除了拖慢存取速度,沒有其他用處翼雀。

工具函數(shù)存在線程安全問題

在前面幾個(gè)函數(shù)中明顯顧及了線程安全問題饱苟,但是在最后幾個(gè)函數(shù)中:例如checkForAliasCircle,hasAlias,canonicalName均并沒有顧及線程安全,在執(zhí)行這些方法的遞歸/循環(huán)時(shí)狼渊,如果Map被其他線程修改箱熬,會返回錯(cuò)誤的結(jié)果。

擴(kuò)展

多線程基礎(chǔ)知識囤锉√沟埽回頭看护锤。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末官地,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子烙懦,更是在濱河造成了極大的恐慌驱入,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件氯析,死亡現(xiàn)場離奇詭異亏较,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)掩缓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進(jìn)店門雪情,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人你辣,你說我怎么就攤上這事巡通〕局矗” “怎么了?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵宴凉,是天一觀的道長誊锭。 經(jīng)常有香客問我,道長弥锄,這世上最難降的妖魔是什么丧靡? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮籽暇,結(jié)果婚禮上温治,老公的妹妹穿的比我還像新娘。我一直安慰自己戒悠,他們只是感情好罐盔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著救崔,像睡著了一般惶看。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上六孵,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天纬黎,我揣著相機(jī)與錄音,去河邊找鬼劫窒。 笑死本今,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的主巍。 我是一名探鬼主播冠息,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼孕索!你這毒婦竟也來了逛艰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤搞旭,失蹤者是張志新(化名)和其女友劉穎散怖,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肄渗,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡镇眷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了翎嫡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片欠动。...
    茶點(diǎn)故事閱讀 40,146評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖惑申,靈堂內(nèi)的尸體忽然破棺而出具伍,到底是詐尸還是另有隱情铆遭,我是刑警寧澤,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布沿猜,位于F島的核電站枚荣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏啼肩。R本人自食惡果不足惜橄妆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望祈坠。 院中可真熱鬧害碾,春花似錦、人聲如沸赦拘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽躺同。三九已至阁猜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蹋艺,已是汗流浹背剃袍。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留捎谨,地道東北人民效。 一個(gè)月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像涛救,于是被迫代替她去往敵國和親畏邢。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評論 2 356

推薦閱讀更多精彩內(nèi)容

  • Java SE 基礎(chǔ): 封裝检吆、繼承舒萎、多態(tài) 封裝: 概念:就是把對象的屬性和操作(或服務(wù))結(jié)合為一個(gè)獨(dú)立的整體,并盡...
    Jayden_Cao閱讀 2,110評論 0 8
  • 一 基礎(chǔ)篇 1.1 Java基礎(chǔ) 面向?qū)ο蟮奶卣鞒橄?將一類對象的共同特征總結(jié)出來構(gòu)建類的過程咧栗。繼承:對已有類的一...
    essential_note閱讀 696評論 0 0
  • 一.線程安全性 線程安全是建立在對于對象狀態(tài)訪問操作進(jìn)行管理逆甜,特別是對共享的與可變的狀態(tài)的訪問 解釋下上面的話: ...
    黃大大吃不胖閱讀 842評論 0 3
  • 不足的地方請大家多多指正虱肄,如有其它沒有想到的常問面試題請大家多多評論致板,一起成長,感謝!~ String可以被繼承嗎...
    啟示錄是真的閱讀 2,940評論 3 3
  • 九種基本數(shù)據(jù)類型的大小咏窿,以及他們的封裝類斟或。(1)九種基本數(shù)據(jù)類型和封裝類 (2)自動(dòng)裝箱和自動(dòng)拆箱 什么是自動(dòng)裝箱...
    關(guān)瑋琳linSir閱讀 1,887評論 0 47