【小家Spring】分享Spring中一个小巧而优雅的类SimpleAliasRegistry源码分析(别名注册、管理器)

扫码关注公众号:Java 技术驿站

发送:vip
将链接复制到本浏览器,永久解锁本站全部文章

【公众号:Java 技术驿站】 【加作者微信交流技术,拉技术群】

每篇一句

世界上只有两种东西有真正的价值,一种是创世人所未见,一种是对已见之事的推进与优化,做一个比现有的还烂的东西,本身是没价值的,只能当作练习使用,这句话的逻辑已经很明晰了,不接受反驳

前言

上一篇分享了:【小家Spring】一文读懂Spring中的BeanFactory和FactoryBean的区别。为了缓解疲劳,本文来个小插曲~~不用费太多脑力的

Spring是一个非常优秀且流行的框架,里面不乏有很多优秀的设计模式、设计思想。

本文主要针对其中一个非常小巧的类:SimpleAliasRegistry做一个源码解读。顺便也分享给大家,若有分析得不到位的地方,非常欢迎指正,毕竟我也是第一次看。

分析此类的源码是因为此类很具有代表性,可以部分代表Spring的代码功底,优雅~~~因为群里有好几次提到过说此类虽然很小巧,但是代码设计得很优雅

初识

首先看到,这个类实现了接口AliasRegistry,而这个接口顾名思义:它就是别名管理器。Spring提供了一个默认实现:SimpleAliasRegistry。内部会缓存这些别名和真实名称的对应关系

在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"/>

到这个时候,可能很多人就会问了,当前都SpringBoot环境了,哪还会用到xml了啊,所以别名这个在Boot这就不会再用了。

其实不然,SpringBoot中我们配置一个Bean一般会这么来写:

    @Configuration
    public class ApplicationConfig {

        @Bean
        public Object object() {
            return new Object();
        }

    }

这个时候我打断点,发现还真的没有注册别名。而@Bean注解里也并没有alias等相关属性,是不是Boot就真的不支持了呢?

其实,只支持的。@Bean虽然没有alias属性,但是它的名称可以是数组,可以写多个名称,而经过我实现发现。当只写一个值的时候,只有名称没有别名。但是当你写多个值的时候,除了第一个是名称,后面的全都是别名。

        @Bean(value = {"aaa", "bbb", "ccc"})
        public Object object() {
            return new Object();
        }

断点启动:
20191102100720\_1.png
20191102100720\_2.png
证实了我上面的结论。虽然别名我们用得挺少,特别是在当下的Boot环境了。但是强大的Spring还是支持的。

当然这并不是本文讨论的重点,重点还是看“优雅的”代码:

    public interface AliasRegistry {
        //增 给name新增一个别名alias
        void registerAlias(String name, String alias);
        //删 删除一个别名
        void removeAlias(String alias);
        //此name是否含有别名
        boolean isAlias(String name);
        //获取此name对应的所有的别名
        String[] getAliases(String name);

    }

它的实现类有不少,此处我们只看SimpleAliasRegistry :

    public class SimpleAliasRegistry implements AliasRegistry {

        /** Logger available to subclasses. */
        protected final Log logger = LogFactory.getLog(getClass());

        /** Map from alias to canonical name. */
        private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);

        @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)) {
                    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) {
                        if (registeredName.equals(name)) {
                            // An existing alias - no need to re-register
                            return;
                        }
                        if (!allowAliasOverriding()) {
                            throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +
                                    name + "': It is already registered for name '" + registeredName + "'.");
                        }
                        if (logger.isDebugEnabled()) {
                            logger.debug("Overriding alias '" + alias + "' definition for registered name '" +
                                    registeredName + "' with new target name '" + name + "'");
                        }
                    }
                    checkForAliasCircle(name, alias);
                    this.aliasMap.put(alias, name);
                    if (logger.isTraceEnabled()) {
                        logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'");
                    }
                }
            }
        }

        /** * Return whether alias overriding is allowed. * Default is {@code true}. */
        protected boolean allowAliasOverriding() {
            return true;
        }

        /** * Determine whether the given name has the given alias registered. * @param name the name to check * @param alias the alias to look for * @since 4.2.1 */
        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();
                    if (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias)) {
                        return true;
                    }
                }
            }
            return false;
        }

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

        /** * Resolve all alias target names and aliases registered in this * factory, applying the given StringValueResolver to them. * <p>The value resolver may for example resolve placeholders * in target bean names and even in alias names. * @param valueResolver the StringValueResolver to apply */
        public void resolveAliases(StringValueResolver valueResolver) {
            Assert.notNull(valueResolver, "StringValueResolver must not be null");
            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)) {
                                // Pointing to existing alias - just remove placeholder
                                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);
                    }
                });
            }
        }

        /** * Check whether the given name points back to the given alias as an alias * in the other direction already, catching a circular reference upfront * and throwing a corresponding IllegalStateException. * @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");
            }
        }

        /** * Determine the raw name, resolving aliases to canonical names. * @param name the user-specified name * @return the transformed name */
        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;
        }

    }

本文就是重点分析此类,代码行数若除去注释,100行左右,但是里面的写法确实是有不少可圈可点的地方。

Tips:此类在Spring中都是被Bean定义、创建的时候继承使用,和Bean的定义相关联
20191102100720\_3.png

为了便于理解,我这里把最重要的一个方法:registerAlias(String name, String alias)画出逻辑流程图如下:
20191102100720\_4.png

源码步骤分析

首先,使用了一个线程安全的Map:ConcurrentHashMap当做注册表来缓存alias和name的对应关系。key为alias别名,value为name真实值。可以通过别名获取到真实值,进而获取到Bean实例

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

为了方便大家下面阅读源码更好的理解为什么这么做?我在这里先说明两点:
1、需要避免循环别名和name之间循环引用的问题。比如a->b b->c c->a这就循环引用了,是需要避免的,否则很容易出问题
2、不能出现并发问题直接出现如下情况。a -> b b->a (其实也是一种循环引用嘛)

首先我们看一下registerAlias方法,也是最为复杂点的方法:

        @Override
        public void registerAlias(String name, String alias) {
            Assert.hasText(name, "'name' must not be empty");
            Assert.hasText(alias, "'alias' must not be empty");

            //此处注意:很多人疑问的地方,用了ConcurrentHashMap,为何此处还要加锁呢?有必要吗?
            //答:非常有必要的。因为ConcurrentHashMap只能保证单个put、remove方法的原子性。而不能保证多个操作同时的原子性。比如我一边添加、一边删除 显然这是不被允许的
            synchronized (this.aliasMap) {
                //若发现别名和name是相同的,就不需要做啥了。而且顺手把这个key给移除掉
                if (alias.equals(name)) {
                    this.aliasMap.remove(alias);
                    if (logger.isDebugEnabled()) {
                        logger.debug("Alias definition '" + alias + "' ignored since it points to same name");
                    }
                } else {
                    //拿到这个别名对应的name,看看该别名是否已经存在对应的name了
                    String registeredName = this.aliasMap.get(alias);
                    if (registeredName != null) {
                        //若已经存在对应的name了,而且还和传进俩的name相同,那啥都不做就行
                        if (registeredName.equals(name)) {
                            return;
                        }

                        //若存在对应的name了,切还不让复写此别名(让其指向别的name),那就跑错吧
                        if (!allowAliasOverriding()) {
                            throw new IllegalStateException("Cannot define alias '" + alias + "' for name '" +
                                    name + "': It is already registered for name '" + registeredName + "'.");
                        }
                        if (logger.isDebugEnabled()) {
                            logger.debug("Overriding alias '" + alias + "' definition for registered name '" +
                                    registeredName + "' with new target name '" + name + "'");
                        }
                    }

                    checkForAliasCircle(name, alias);
                    this.aliasMap.put(alias, name);
                    if (logger.isTraceEnabled()) {
                        logger.trace("Alias definition '" + alias + "' registered for name '" + name + "'");
                    }
                }
            }
        }

当alias对应的name不存在的时候,还需要去检查是否可能存在循环引用现象:checkForAliasCirclee如下:

        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方法如下:

        public boolean hasAlias(String name, String alias) {
            for (Map.Entry<String, String> entry : this.aliasMap.entrySet()) {
                String registeredName = entry.getValue();

                //若找到了此name 然后就拿出其对应的alias(可能会有多次哦)
                if (registeredName.equals(name)) {
                    String registeredAlias = entry.getKey();

                    //如果此alias和传入的alias相同,返回true 证明name有这个alias
                    //一般人可能上面那一步就算了直接return了,但是,但是,但是还有一种情况也必须考虑到:倘若这个已经注册过的registeredAlias和传入的alias不相等。
                    //但是把他作为name去找是否有alias的时候,如果有也得判断是true,表示有。 防止了a -> b b->c c->a的循环的情况 此处处理可以说是非常的优雅和谨慎了~
                    if (registeredAlias.equals(alias) || hasAlias(registeredAlias, alias)) {
                        return true;
                    }
                }
            }
            return false;
        }

该方法旨在判断:给定的name,是否已经有了对应的alias别名呢?

注册的难点在于如何防止重现名称和别名之间的重复引用。

最后用一张图形象的解释一下,为什么需要加锁?

在注册别名时,检查别名是否注册过名称这一步,如果不对注册表加锁,会导致检查出现问题,最终导致出现重复引用
20191102100720\_5.png
两个注册过程并发进行,在检查时两个注册过程均未发现问题,但是注册过后便会出现问题
20191102100720\_6.png

知识交流

20191102100720\_7.png
若群二维码失效,请加微信号(或者扫描下方二维码):fsx641385712。
并且备注:“java入群” 字样,会手动邀请入群

20191102100720\_8.png


来源:http://ddrv.cn

赞(0) 打赏
版权归原创作者所有,任何形式的转载请联系博主:daming_90:Java 技术驿站 » 【小家Spring】分享Spring中一个小巧而优雅的类SimpleAliasRegistry源码分析(别名注册、管理器)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏