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

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

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


image.png


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


image.png


image.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别名呢?


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


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


在注册别名时,检查别名是否注册过名称这一步,如果不对注册表加锁,会导致检查出现问题,最终导致出现重复引用

image.png




两个注册过程并发进行,在检查时两个注册过程均未发现问题,但是注册过后便会出现问题

image.png

相关文章
|
9月前
|
存储 Java 数据库
Spring Boot 注册登录系统:问题总结与优化实践
在Spring Boot开发中,注册登录模块常面临数据库设计、密码加密、权限配置及用户体验等问题。本文以便利店销售系统为例,详细解析四大类问题:数据库字段约束(如默认值缺失)、密码加密(明文存储风险)、Spring Security配置(路径权限不当)以及表单交互(数据丢失与提示不足)。通过优化数据库结构、引入BCrypt加密、完善安全配置和改进用户交互,提供了一套全面的解决方案,助力开发者构建更 robust 的系统。
320 0
|
监控 Java 应用服务中间件
Spring Boot整合Tomcat底层源码分析
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置和起步依赖等特性,大大简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是其与Tomcat的整合。
367 1
|
Java Spring
【Spring】方法注解@Bean,配置类扫描路径
@Bean方法注解,如何在同一个类下面定义多个Bean对象,配置扫描路径
591 73
|
9月前
|
前端开发 IDE Java
Spring MVC 中因导入错误的 Model 类报错问题解析
在 Spring MVC 或 Spring Boot 开发中,若导入错误的 `Model` 类(如 `ch.qos.logback.core.model.Model`),会导致无法解析 `addAttribute` 方法的错误。正确类应为 `org.springframework.ui.Model`。此问题通常因 IDE 自动导入错误类引起。解决方法包括:删除错误导入、添加正确包路径、验证依赖及清理缓存。确保代码中正确使用 Spring 提供的 `Model` 接口以实现前后端数据传递。
308 0
|
设计模式 XML Java
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
本文详细介绍了Spring框架的核心功能,并通过手写自定义Spring框架的方式,深入理解了Spring的IOC(控制反转)和DI(依赖注入)功能,并且学会实际运用设计模式到真实开发中。
【23种设计模式·全精解析 | 自定义Spring框架篇】Spring核心源码分析+自定义Spring的IOC功能,依赖注入功能
|
12月前
|
监控 Java 应用服务中间件
SpringBoot是如何简化Spring开发的,以及SpringBoot的特性以及源码分析
Spring Boot 通过简化配置、自动配置和嵌入式服务器等特性,大大简化了 Spring 应用的开发过程。它通过提供一系列 `starter` 依赖和开箱即用的默认配置,使开发者能够更专注于业务逻辑而非繁琐的配置。Spring Boot 的自动配置机制和强大的 Actuator 功能进一步提升了开发效率和应用的可维护性。通过对其源码的分析,可以更深入地理解其内部工作机制,从而更好地利用其特性进行开发。
498 6
|
Java Spring
【Spring配置相关】启动类为Current File,如何更改
问题场景:当我们切换类的界面的时候,重新启动的按钮是灰色的,不能使用,并且只有一个Current File 项目,下面介绍两种方法来解决这个问题。
278 10
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
158 1
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
174 1
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
137 0