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

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

前言


上一篇分享了:【小家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();
    }


断点启动:


image.png


image.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行左右,但是里面的写法确实是有不少可圈可点的地方。

相关文章
|
2月前
|
XML Java 测试技术
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
这篇文章介绍了Spring5框架的三个新特性:支持@Nullable注解以明确方法返回、参数和属性值可以为空;引入函数式风格的GenericApplicationContext进行对象注册和管理;以及如何整合JUnit5进行单元测试,同时讨论了JUnit4与JUnit5的整合方法,并提出了关于配置文件加载的疑问。
Spring5入门到实战------17、Spring5新功能 --Nullable注解和函数式注册对象。整合JUnit5单元测试框架
|
9天前
|
XML 缓存 Java
spring源码剖析-spring-beans(内部核心组件,BeanDefinition的注册,BeanWapper创建)
spring源码剖析-spring-beans(内部核心组件,BeanDefinition的注册,BeanWapper创建)
38 10
|
2月前
|
缓存 Java 开发者
Spring高手之路22——AOP切面类的封装与解析
本篇文章深入解析了Spring AOP的工作机制,包括Advisor和TargetSource的构建与作用。通过详尽的源码分析和实际案例,帮助开发者全面理解AOP的核心技术,提升在实际项目中的应用能力。
23 0
Spring高手之路22——AOP切面类的封装与解析
|
3月前
|
Java Spring
idea新建spring boot 项目右键无package及java类的选项
idea新建spring boot 项目右键无package及java类的选项
103 5
|
3月前
|
Java Spring
spring cloud gateway在使用 zookeeper 注册中心时,配置https 进行服务转发
spring cloud gateway在使用 zookeeper 注册中心时,配置https 进行服务转发
68 3
|
3月前
|
Java 数据库连接 API
Spring事务管理嵌套事务详解 : 同一个类中,一个方法调用另外一个有事务的方法
Spring事务管理嵌套事务详解 : 同一个类中,一个方法调用另外一个有事务的方法
|
3月前
|
负载均衡 安全 Java
Spring Cloud中的服务发现与注册
Spring Cloud中的服务发现与注册
|
3月前
|
负载均衡 Java 微服务
深入理解Spring Cloud中的服务发现与注册
深入理解Spring Cloud中的服务发现与注册
|
3月前
|
Java API 数据中心
Spring Cloud中的服务注册与发现实现方法
Spring Cloud中的服务注册与发现实现方法
|
3月前
|
负载均衡 Java API
Spring Cloud中的服务注册与发现策略
Spring Cloud中的服务注册与发现策略
下一篇
无影云桌面