Spring的SimpleAliasRegistry类解析

近期在看Spring的源码,看到了一个类SimpleAliasRegistry,觉得这个类虽然小,但是代码写的很不错,下面简单解析下这个类。SimpleAliasRegistry有一个属性aliasMap,是一个ConcurrentHashMap,用来存放bean的别名。在注册别名时使用的是方法:registerAlias,这是一个接口方法,继承自AliasRegistry。下面是具体方法:

@Override
    public void registerAlias(String name, String alias) {
        Assert.hasText(name, "'name' must not be empty");
        Assert.hasText(alias, "'alias' must not be empty");
        if (alias.equals(name)) {
            this.aliasMap.remove(alias);
        }
        else {
            String registeredName = this.aliasMap.get(alias);
            if (registeredName != null) {
                if (registeredName.equals(name)) {
                    // An existing alias - no need to re-register
                    return;
                }
                if (!allowAliasOverriding()) {
                    throw new IllegalStateException("Cannot register alias '" + alias + "' for name '" +
                            name + "': It is already registered for name '" + registeredName + "'.");
                }
            }
            checkForAliasCircle(name, alias);
            this.aliasMap.put(alias, name);
        }
    }

这个方法首先断言两个参数必须含有有效字符串。如果别名和名称相同,则移除这个别名。
在名称和别名不同时,获取这个别名对应的bean名称(这个名称有可能也是个别名)。如果该别名对应的名称有两种可能:存在或不存在。先来看看存在的情况:如果该别名对应的已注册的name和传入的name相同,则不需重复注册。然后检查别名是否允许重写,不允许则抛出异常(默认允许)。然后对别名进行循环检查:

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

这个方法只是简单调用下hasAlias方法,并将要注册的alias和name传入,这里是将aliase作为第一个参数、name作为第二个参数。下面看hasAlias方法的代码:

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();
                return (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias));
            }
        }
        return false;
    }

注意这里的name是要注册测alias,而aliase是最开始注册时传入的name。这步检查的是别名是否是循环引用了。即找到以当前注册的别名为value的entity,看下这个entity的key是不是和当前注册的这个别名对应的name相等,如果是,则属于循环引用;如果不是,则递归找到以上面那个entity的key为value的entity(这里称为B),如果B的对应的key和当前要注册的name相等,则也属于是循环引用。

下面再拉看下获取给定的bean对应的所有别名。
首先获取给定bean的canonicalName,从字面就能理解是什么意思,通过以下方法获取:

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

这个方法就是根据给定的名字获取对应bean的canonicalName;在内部的Map中,所有的key都是aliase,如果一个entity的value在这个Map中没有对应的entity的key与其相等,则这个entity中的value就是一个bean的canonicalName。获取了bean的canonicalName之后就能以这个name为参数获取这个bean的所有aliase了,看下面的方法:

@Override
    public String[] getAliases(String name) {
        List<String> result = new ArrayList<String>();
        synchronized (this.aliasMap) {
            retrieveAliases(name, result);
        }
        return StringUtils.toStringArray(result);
    }

锁住内部Map之后调用retrieveAliases(name, result)方法。为什么要锁住这个内部Map,可以看下CurrentHashMap的解释,简单说就是CurrentHashMap迭代方法不是线程安全的。下面看下获取所有aliase的方法:

   private void retrieveAliases(String name, List<String> result) {
        for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) {
            String registeredName = entry.getValue();
            if (registeredName.equals(name)) {
                String alias = entry.getKey();
                result.add(alias);
                retrieveAliases(alias, result);
            }
        }
    }

这个方法就很简单了,获取所有以bean的canonicalName为value的entity将其key放入一个List中,然后递归的获取以当前的entity的key为value的entity将其key放入上面那个list中。
了解了这个内部Map的作用后下面两个方法就很清楚了:

@Override
    public void removeAlias(String alias) {
        String name = this.aliasMap.remove(alias);
        if (name == null) {
            throw new IllegalStateException("No alias '" + alias + "' registered");
        }
    }

这个方法就是移除一个别名,如果这个别名不存在则抛出IllegalStateException异常。

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

因为在内部的Map中Bean的canonicalName总是作为entity的value存在的,所以只用判断当前的Map中有没有给定的key即可知道是不是别名了。

最后来看下resolveAliases这个方法,先上代码:

public void resolveAliases(StringValueResolver valueResolver) {
        Assert.notNull(valueResolver, "StringValueResolver must not be null");
        synchronized (this.aliasMap) {
            Map<String, String> aliasCopy = new HashMap<String, String>(this.aliasMap);
            for (String alias : aliasCopy.keySet()) {
                String registeredName = aliasCopy.get(alias);
                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)) {
                            // Pointing to existing alias - just remove placeholder
                            this.aliasMap.remove(alias);
                            break;
                        }
                        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);
                }
            }
        }
    }

首先还是先锁住内部的Map,因为CurrentHashMap只有put和remove是线程安全的。先看比较简单的分支情况:如果解析后的别名或者canonicalName为空或者是别名与canonicalName相同,则移除,与registerAlias方法的逻辑相同;如果解析后的canonicalName和现在Map中的canonicalName不同,则替换。下面是不属于上面两种情况的一个分支:
如果解析后的别名和原始别名不同,查看内部Map中以解析后的别名为key的项,如果不存在,则检查完循环引用后删除原始别名,添加解析后的别名;如果项存在,且存在项的value和解析后的name相同,只用将以原始aliase为key的entity移除即可,如果存在项的value和解析后的name不同,则说明解析后的aliase已被占用了,则抛出IllegalStateException

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值