关于spring:Spring-Bean-别名处理原理分析

1次阅读

共计 8331 个字符,预计需要花费 21 分钟才能阅读完成。

明天来和小伙伴们聊一聊 Spring 中对于 Bean 别名的解决逻辑。

1. Alias

别名,顾名思义就是给一个 Bean 去两个甚至多个名字。整体上来说,在 Spring 中,有两种不同的别名定义形式:

  1. 定义 Bean 的 name 属性,name 属性在真正的处理过程中,实际上就是依照别名来解决的。
  2. 通过 alias 标签定义专门的别名,通过 alias 定义进去的别名和 name 属性定义的别名最终都是合并在一起解决的,所以 这两种定义别名的形式最终是必由之路

那么定义的别名是保留在哪里呢?

大家晓得,Bean 解析进去之后被保留在容器中,别名其实也是一样的,容器中存在一个 aliasMap 专门用来保留 Bean 的别名,保留的格局是 alias->name,例如有一个 Bean 的名字是 user,别名是 userAlias,那么保留在 aliasMap 中就是 userAlias->user。

举个简略例子:

<bean class="org.javaboy.demo.User" id="user" name="user4,user5,user6"/>
<alias name="user" alias="user2"/>
<alias name="user2" alias="user3"/>

在下面这段定义中,user2、user3、user4、user5、user6 都是别名。

2. AliasRegistry

2.1 AliasRegistry

Spring 中为别名的解决提供了 AliasRegistry 接口,这个接口中提供了别名解决的次要办法:

public interface AliasRegistry {void registerAlias(String name, String alias);
    void removeAlias(String alias);
    boolean isAlias(String name);
    String[] getAliases(String name);
}
  • registerAlias:这个办法用来增加别名,外围逻辑就是向 aliasMap 中增加数据。
  • removeAlias:这个办法用来从 aliasMap 中移除一个别名。
  • isAlias:判断给定的 name 是否是一个别名。
  • getAliases:依据给定的名字去获取所有的别名。

办法就这四个,看一下这个接口的实现类有哪些。

大家看到,AliasRegistry 的实现类其实还是蛮多的,然而大部分都是容器,真正实现了 AliasRegistry 中四个办法的只有 SimpleAliasRegistry,其余的容器大部分其实都是为了具备别名治理的能力,继承了 SimpleAliasRegistry。

所以真正给咱们整活的其实是 SimpleAliasRegistry。

2.2 SimpleAliasRegistry

SimpleAliasRegistry 类中的内容比拟多,为了解说不便,我就挨个贴属性和办法进去,贴出来后和大家分享。

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

首先,SimpleAliasRegistry 中定义了一个 aliasMap,这个就是用来保留别名的,这是一个 Map 汇合,接下来所有的操作都是围绕这个汇合开展。

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

这个办法用来移除别名,移除的思路很简略,就是从 aliasMap 中移除数据即可,如果 remove 办法返回值为 null 那就阐明要移除的别名不存在,那么间接抛出异样。

@Override
public boolean isAlias(String name) {return this.aliasMap.containsKey(name);
}

这个是判断是否蕴含某一个别名,这个判断简略。有一个跟它容易产生歧义的办法,如下:

public boolean hasAlias(String name, String alias) {String registeredName = this.aliasMap.get(alias);
    return ObjectUtils.nullSafeEquals(registeredName, name) ||
            (registeredName != null && hasAlias(name, registeredName));
}

这个办法是判断给定的 name 和 alias 之间是否具备关联关系。判断的逻辑就是先去 aliasMap 中,依据 alias 查出来这个 alias 所对应的实在 beanName,即 registeredName,而后判断 registeredName 和 name 是否相等,如果相等就间接返回,如果不相等就持续递归调用,为什么要递归呢?因为 aliasMap 中存在的别名可能是这样的:

  • a->b
  • b->c
  • c->d

即 a 是 b 的别名,b 是 c 的别名,c 是 d 的别名,当初如果想要判断 a 和 d 之间的关系,那么依据 a 查出来的 b 显然不等于 d,所以要持续递归,再依据 b 查 c,依据 c 查到 d,这样就能确定 a 和 d 是否有关系了。

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

getAliases 办法是依据传入的 name 找到其对应的别名,然而因为别名可能存在多个,所以调用 retrieveAliases 办法递归去查找所有的别名,将找到的别名都存入到一个汇合中,最终将汇合转为数组返回。

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

这个办法用来查看别名是否存在死结,即 a 是 b 的别名,b 是 a 的别名这种状况。查看的形式很简略,就是调用 hasAlias 办法,然而将传入的两个参数颠倒过去就能够了。

public void resolveAliases(StringValueResolver valueResolver) {synchronized (this.aliasMap) {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)) {this.aliasMap.remove(alias);
            }
            else if (!resolvedAlias.equals(alias)) {String existingName = this.aliasMap.get(resolvedAlias);
                if (existingName != null) {if (existingName.equals(resolvedName)) {this.aliasMap.remove(alias);
                        return;
                    }
                    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);
            }
            else if (!registeredName.equals(resolvedName)) {this.aliasMap.put(alias, resolvedName);
            }
        });
    }
}

这个办法是解决别名是占位符的状况,例如当引入了一个 .properties 文件之后,那么在配置别名的时候就能够援用 .properties 中的变量,那么下面这个办法就是用来解析变量的。

例如上面这种状况,我有一个 alias.properties,如下:

name=user
alias=javaboy

而后在 XML 文件中应用这个 properties 文件,如下:

<context:property-placeholder location="classpath:alias.properties"/>
<alias name="${name}" alias="${alias}"/>

对于这种状况,一开始存入到 aliasMap 中的就是占位符了,resolveAliases 办法就是要将这些占位符解析为具体的字符串。

大家看到,首先这里将 aliasMap 复制一份,生成一个 aliasCopy,而后进行遍历。在遍历时,依据 valueResolver 将援用应用的占位符解析为真正的字符,如果解析进去的。如果解析进去的 name 和别名是雷同的,那么显然是有问题的,就须要把这个别名移除掉。

持续判断,如果解析进去的别名和本来的别名不相等(阐明别名应用了占位符),那么就去检查一下这个别名对应的 name,如果这个 name 曾经存在,且等于占位符解析进去的 name,阐明这个别名曾经被定义过了,即反复定义,那么就把别名移除掉即可。如果这个别名指向的 name 和占位符解析进去的 name 不相等,阐明试图让一个别名指向两个 bean,那么就间接抛出异样了。

如果解析进去的别名还没有指向 name 属性的话,那么就失常解决,查看是否存在死结、移除带占位符的别名,存入解析之后的别名。

最初,如果本来的名称和解析之后的属性名称不相等,那么就间接保留这个别名即可。

@Override
public void registerAlias(String name, String alias) {synchronized (this.aliasMap) {if (alias.equals(name)) {this.aliasMap.remove(alias);
        }
        else {String registeredName = this.aliasMap.get(alias);
            if (registeredName != null) {if (registeredName.equals(name)) {return;}
                if (!allowAliasOverriding()) {
                    throw new IllegalStateException("Cannot define alias'" + alias + "'for name'" +
                            name + "': It is already registered for name'" + registeredName + "'.");
                }
            }
            checkForAliasCircle(name, alias);
            this.aliasMap.put(alias, name);
        }
    }
}

这个就是应用最多的别名注册了,传入的参数别离是 bean 的 name 和 alias,如果 alias 跟 name 相等,二话不说间接移除,这个 alias 有问题。

否则就去查问这个 alias,查看这个 alias 是否曾经有对应的 name 了,如果有,且等于传入的 name,那么间接返回就行了,不必注册,因为曾经注册过了;如果有且不等于传入的 name,那么就抛出异样,因为一个 alias 不能指向两个 name。最初就是检查和保留了。

public String canonicalName(String name) {
    String canonicalName = name;
    String resolvedName;
    do {resolvedName = this.aliasMap.get(canonicalName);
        if (resolvedName != null) {canonicalName = resolvedName;}
    }
    while (resolvedName != null);
    return canonicalName;
}

这个办法用来解析进去别名里边顶格的名字,例如有一个 bean 有很多别名,a->b,b->c,c->d,那么这个办法的目标就是传入 a、b、c 中任意一个,返回 d 即可。因为 Spring 容器在解决的时候,并不必管这么多别名问题,容器只须要专一一个名字即可,因为最初一个别名实际上就是指向实在的 beanId 了,所以最终拿到的 bean 名称其实相当于 bean 的 ID 了。

别名的解决次要就是这些办法。

3. 原理剖析

后面咱们说了,别名的起源次要是两个中央:name 属性和 alias 标签,咱们别离来看。

3.1 name 解决

对于 name 属性的解决,有两个中央,一个是在 bean 定义解析的时候,将 name 属性解析为 alias,具体在 BeanDefinitionParserDelegate#parseBeanDefinitionElement 办法中(这个办法在之前跟大家讲 bean 的默认名称生成策略的时候,见过):

@Nullable
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {String id = ele.getAttribute(ID_ATTRIBUTE);
    String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
    List<String> aliases = new ArrayList<>();
    if (StringUtils.hasLength(nameAttr)) {String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
        aliases.addAll(Arrays.asList(nameArr));
    }
    // 省略其余
}

能够看到,这里就从 XML 节点中提取进去 name 属性,而后切分为一个数组,并将之存入到 aliases 属性中。接下来在后续的 BeanDefinitionReaderUtils#registerBeanDefinition 办法中,再把 aliases 中的值注册一下,如下:

public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
        throws BeanDefinitionStoreException {String beanName = definitionHolder.getBeanName();
    registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
    String[] aliases = definitionHolder.getAliases();
    if (aliases != null) {for (String alias : aliases) {registry.registerAlias(beanName, alias);
        }
    }
}

这就是 XML 中的 name 属性是如何变为别名的。

3.2 别名标签解决

别名的另一个起源是别名标签,在 Spring 解析 XML 标签的时候,有针对别名标签的专门解决,具体位置是在 DefaultBeanDefinitionDocumentReader#parseDefaultElement 办法中:

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {importBeanDefinitionResource(ele);
    }
    else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {processAliasRegistration(ele);
    }
    else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {processBeanDefinition(ele, delegate);
    }
    else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
        // recurse
        doRegisterBeanDefinitions(ele);
    }
}

这里会去判断标签的类型,如果是别名,就调用 processAliasRegistration 办法进行解决:

protected void processAliasRegistration(Element ele) {String name = ele.getAttribute(NAME_ATTRIBUTE);
    String alias = ele.getAttribute(ALIAS_ATTRIBUTE);
    boolean valid = true;
    if (!StringUtils.hasText(name)) {getReaderContext().error("Name must not be empty", ele);
        valid = false;
    }
    if (!StringUtils.hasText(alias)) {getReaderContext().error("Alias must not be empty", ele);
        valid = false;
    }
    if (valid) {
        try {getReaderContext().getRegistry().registerAlias(name, alias);
        }
        catch (Exception ex) {getReaderContext().error("Failed to register alias'" + alias +
                    "'for bean with name'" + name + "'", ele, ex);
        }
        getReaderContext().fireAliasRegistered(name, alias, extractSource(ele));
    }
}

能够看到,这里也是从 XML 文件中的别名标签上,提取进去 name 和 alias 属性值,最初调用 registerAlias 办法进行注册。

好啦,这就是 Spring 中对于别名的解决流程啦~

正文完
 0