背景簡介
出現(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è) Map
的key
是別名彼城,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
在操作中沒有任何幫助趋距,原因如下:
- 實(shí)例屬性為
private
且沒有getter
粒氧,不存在泄漏可能 - 所有方法均為復(fù)雜操作,需要自行加鎖节腐,無法直接委托給
ConcurrentHashMap
綜上外盯,使用ConcurrentHashMap
除了拖慢存取速度,沒有其他用處翼雀。
工具函數(shù)存在線程安全問題
在前面幾個(gè)函數(shù)中明顯顧及了線程安全問題饱苟,但是在最后幾個(gè)函數(shù)中:例如checkForAliasCircle
,hasAlias
,canonicalName
均并沒有顧及線程安全,在執(zhí)行這些方法的遞歸/循環(huán)時(shí)狼渊,如果Map
被其他線程修改箱熬,會返回錯(cuò)誤的結(jié)果。
擴(kuò)展
多線程基礎(chǔ)知識囤锉√沟埽回頭看护锤。