Spring 获取单例流程(二)

简介: 读完这篇文章你将会收获到• Spring 中 prototype 类型的 bean 如何做循环依赖检测• Spring 中 singleton 类型的 bean 如何做循环依赖检测

读完这篇文章你将会收获到

  • Springprototype 类型的 bean 如何做循环依赖检测
  • Springsingleton 类型的 bean 如何做循环依赖检测


前言


继上一篇文章 Spring 获取单例流程(一) 我们这次继续往下分析一下后面的流程

image-20200530152154079

上一篇文章中我们说到,首先我们根据 name 找到其对应的 beanName 、然后去缓存中看是否已经创建了/创建中这个对应的 bean,如果在缓存中找到了这个 bean、那么我们需要对这个 bean 可能进行一些处理,比如说用户想要的是一个普通的 bean 、但是在 Spring 缓存中找到的是一个 factoryBean、这个时候就要调用 fatoryBeangetObject 方法以及对应的一些前置方法以及回调等。

那么如果我们在缓存中找不到这个 bean 那么流程又是怎么样?这个就是这篇文章要跟大家一起分享的


源码分析


if (sharedInstance != null && args == null) {
   // 这里被我删除了一些spring  的log
   // 处理一下 factory bean 的情况、包括从 factory beans 的缓存中获取、或者重新调用 factory bean 的 get bean 方法 包括一些回调
   bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
} else {
   // 从 上面的 getSingleton 拿不到对象的bean 、说明这个bean的scope 要么不是 singleton 要这个bean是singleton 但是没有初始化一句
     //  因为 Spring 只解决单例模式下得循环依赖,在原型模式下如果存在循环依赖则会抛出异常
   // 这里的循环依赖检查使用的 是 threadLocal 因为 prototype 类型的只是
   if (isPrototypeCurrentlyInCreation(beanName)) {
      throw new BeanCurrentlyInCreationException(beanName);
   }
    // 如果容器中没有找到,则从父类容器中加载
   BeanFactory parentBeanFactory = getParentBeanFactory();
   // parentBeanFactory 不为空且 beanDefinitionMap 中不存该 name 的 BeanDefinition
   if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
      // Not found -> check parent.
      // 这里只是找出他的真正的beanName、并没有去掉 factory bean 的前缀
      String nameToLookup = originalBeanName(name);
      if (parentBeanFactory instanceof AbstractBeanFactory) {
         return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
               nameToLookup, requiredType, args, typeCheckOnly);
      } else if (args != null) {
         // Delegation to parent with explicit args.
         return (T) parentBeanFactory.getBean(nameToLookup, args);
      } else if (requiredType != null) {
         // No args -> delegate to standard getBean method.
         return parentBeanFactory.getBean(nameToLookup, requiredType);
      } else {
         return (T) parentBeanFactory.getBean(nameToLookup);
      }
   }
  .......
  .......
  ........
}
复制代码


第一步就是判断这个是否是一个 prototype 类型的 bean,如果是并且正在创建中、那么就抛出一个循环依赖的异常

protected boolean isPrototypeCurrentlyInCreation(String beanName) {
  // prototypesCurrentlyInCreation 是一个 threadLocal
   Object curVal = this.prototypesCurrentlyInCreation.get();
   return (curVal != null &&
         (curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>) curVal).contains(beanName))));
}
复制代码


每一个 prototypebean 创建的时候都会调用下面这个方法

protected void beforePrototypeCreation(String beanName) {
   Object curVal = this.prototypesCurrentlyInCreation.get();
   if (curVal == null) {
      this.prototypesCurrentlyInCreation.set(beanName);
   } else if (curVal instanceof String) {
      Set<String> beanNameSet = new HashSet<>(2);
      beanNameSet.add((String) curVal);
      beanNameSet.add(beanName);
      this.prototypesCurrentlyInCreation.set(beanNameSet);
   } else {
      Set<String> beanNameSet = (Set<String>) curVal;
      beanNameSet.add(beanName);
   }
}
复制代码

curVal 要么是一个 String 要么是一个 Set , 而在创建 prototype bean 完成之后

protected void afterPrototypeCreation(String beanName) {
  Object curVal = this.prototypesCurrentlyInCreation.get();
  if (curVal instanceof String) {
   this.prototypesCurrentlyInCreation.remove();
  } else if (curVal instanceof Set) {
   Set<String> beanNameSet = (Set<String>) curVal;
   beanNameSet.remove(beanName);
   if (beanNameSet.isEmpty()) {
    this.prototypesCurrentlyInCreation.remove();
   }
  }
 }
复制代码


可以看到 Spring 使用 ThreadLocal 去做一个循环依赖的检测、我们在 Spring 资源加载的源码分析里面也提及到了、也是使用 ThreadLocal 进行一个资源的循环引用的检测 Spring 容器的初始化


第二步则是判断当前的 beanFactory 是否有父容器(父 beanFactory) ,如果有并且 beanNamebeanDefinition 不存在当前的 beanFactory 中,那么则尝试在父容器中去获取这个 bean

我们继续往下看下面的代码

// 如果不是仅仅做类型检查则是创建bean,标记这个bean 已经创建了或者将要被创建
if (!typeCheckOnly) {
   markBeanAsCreated(beanName);
}
try {
   //  从容器中获取 beanName 相应的 GenericBeanDefinition,并将其转换为 RootBeanDefinition
   final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
   //  检查给定的合并的 BeanDefinition
   checkMergedBeanDefinition(mbd, beanName, args);
   // Guarantee initialization of beans that the current bean depends on.
   // 处理所依赖的 bean
   String[] dependsOn = mbd.getDependsOn();
   if (dependsOn != null) {
      for (String dep : dependsOn) {
         // 如果是循环依赖
         if (isDependent(beanName, dep)) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                  "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
         }
         // 注册
         registerDependentBean(dep, beanName);
         try {
            // 看看我依赖的大佬好了没
            getBean(dep);
         } catch (NoSuchBeanDefinitionException ex) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                  "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
         }
      }
   }
  ......
  ......
复制代码


第三步则是从 BeanDefinitionRegistry 中获取注册的 BeanDefinition 继而获取这个 bean 所要依赖的其他 bean ,遍历其所依赖的 bean 、判断是否循环依赖了

protected boolean isDependent(String beanName, String dependentBeanName) {
   synchronized (this.dependentBeanMap) {
      return isDependent(beanName, dependentBeanName, null);
   }
}
private boolean isDependent(String beanName, String dependentBeanName, @Nullable Set<String> alreadySeen) {
   if (alreadySeen != null && alreadySeen.contains(beanName)) {
      return false;
   }
   String canonicalName = canonicalName(beanName);
   // 找出依赖这个beanName的集合
   Set<String> dependentBeans = this.dependentBeanMap.get(canonicalName);
   // 没有人依赖这个beanName
   if (dependentBeans == null) {
      return false;
   }
   // 哦嚯、beanName 依赖的 bean、也依赖着beanName、完蛋
   if (dependentBeans.contains(dependentBeanName)) {
      return true;
   }
   // 看看依赖 beanName 的 其他依赖、有没有被dependentBeanName 依赖
   // A 想依赖F、BCDE 依赖着A、那么我们现在来到这一步、已经确定了F不依赖A、那么我们要看看F是否依赖BCDE、如果依赖、那么就是循环依赖了
   for (String transitiveDependency : dependentBeans) {
      if (alreadySeen == null) {
         alreadySeen = new HashSet<>();
      }
      alreadySeen.add(beanName);
      if (isDependent(transitiveDependency, dependentBeanName, alreadySeen)) {
         return true;
      }
   }
   return false;
}
复制代码


每一个 bean 创建之前都会注册其依赖关系、主要由两个 Map 组成、一个是 key 为被依赖者,value 为依赖者集合,另一个则是 key 为依赖者,value 为被依赖者集合,比如说 beanA 依赖着 beanBbeanC

key 为被依赖者 value 为依赖者集合
beanB ---> beanA
beanC ---> beanA
key 为依赖者,value 为被依赖者集合
beanA ---> beanB,beanC
复制代码


第四步则是去注册依赖关系,也就是往上面的两个 Map 中存放数据

public void registerDependentBean(String beanName, String dependentBeanName) {
  String canonicalName = canonicalName(beanName);
  // 在这个里面加上 这个依赖我的人
  synchronized (this.dependentBeanMap) {
   Set<String> dependentBeans =
     this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8));
   if (!dependentBeans.add(dependentBeanName)) {
    return;
   }
  }
  // 在这里将我依赖的 那个大佬放进去我依赖的列表中
  synchronized (this.dependenciesForBeanMap) {
   Set<String> dependenciesForBean =
     this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8));
   dependenciesForBean.add(canonicalName);
  }
 }
复制代码


最后的 getBean 则回到我们最初的起点

getBean(dep);
复制代码


今天我们就先分析到这里、后续的话我们在后面的文章继续探讨。今天我们大致分析了


总结


  • 根据参数中的 name 找出对应的 beanName、无论这个 name 是别名或者是一个 factoryBeanbeanName
  • 查看缓存中是否包含这个 beanName 对象
  • 先从一级缓存 singletonObjects 中看看有没有
  • 然后从二级缓存 earlySingletonObjects
  • 都没有的话再从三级缓存 singletonFactories 中看看有没有
  • 如果缓存中有 bean、那么我们还是需要处理一下这个 bean
  • 如果 Spring 缓存中返回的 beanfactoryBean 、而用户也想要的是一个 beanFactory (参数 name 中的前缀是 & )、那么我们直接返回
  • 如果 Spring 缓存中返回的 bean 是普通的 bean、而用户也想要的是一个普通的 bean 、那么就直接返回
  • 如果 Spring 缓存中返回的 bean 是一个 factoryBean 、而用户想要的是一个普通的 bean 、那么我们就要从 factoryBean 中获取这个 bean
  • 而从 factoryBean 中获取这个 bean 的过程中、需要调用到前置处理、后置处理和我们常用的接口回调 BeanPostProcessor
  • 如果缓存中没有 bean 、则判断是否是 prototype 类型并且循环依赖
  • 如果没有则尝试能否在父容器中找到该 bean
  • 如果父容器也没有则获取该 beanName 对应的 beanDefinition 找出其依赖的 beanName
  • 判断该 beanName 与 依赖的 beanName 是否循环依赖、没有则注册其依赖关系并调用 getBean 方法去创建依赖的 beanName



目录
相关文章
|
6月前
|
缓存 算法 安全
Spring 为啥默认把bean设计成单例的?这篇讲的明明白白的
Spring 为啥默认把bean设计成单例的?这篇讲的明明白白的
89 0
|
6月前
|
XML 前端开发 Java
深入了解Spring MVC工作流程
深入了解Spring MVC工作流程
|
2月前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
138 24
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
25天前
|
XML Java 应用服务中间件
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
【Spring】运行Spring Boot项目,请求响应流程分析以及404和500报错
114 2
|
6月前
|
安全 Java Spring
Spring框架中的单例Bean是线程安全的吗?
Spring框架中的单例Bean是线程安全的吗?
78 1
|
20天前
|
JSON 前端开发 JavaScript
优雅!Spring Boot 3.3 实现职责链模式,轻松应对电商订单流程
本文介绍如何使用 Spring Boot 3.3 实现职责链模式,优化电商订单处理流程。通过将订单处理的各个环节(如库存校验、优惠券核验、支付处理等)封装为独立的处理器,并通过职责链将这些处理器串联起来,实现了代码的解耦和灵活扩展。具体实现包括订单请求类 `OrderRequest`、抽象处理器类 `OrderHandler`、具体处理器实现(如 `OrderValidationHandler`、`VerifyCouponHandler` 等)、以及初始化职责链的配置类 `OrderChainConfig`。
|
3月前
|
安全 Java C#
Spring创建的单例对象,存在线程安全问题吗?
Spring框架提供了多种Bean作用域,包括单例(Singleton)、原型(Prototype)、请求(Request)、会话(Session)、全局会话(GlobalSession)等。单例是默认作用域,保证每个Spring容器中只有一个Bean实例;原型作用域则每次请求都会创建一个新的Bean实例;请求和会话作用域分别与HTTP请求和会话绑定,在Web应用中有效。 单例Bean在多线程环境中可能面临线程安全问题,Spring容器虽然确保Bean的创建过程是线程安全的,但Bean的使用安全性需开发者自行保证。保持Bean无状态是最简单的线程安全策略;
|
4月前
|
Java 持续交付 Maven
Spring Boot程序的打包与运行:构建高效部署流程
构建高效的Spring Boot部署流程对于保障应用的快速、稳定上线至关重要。通过采用上述策略,您可以确保部署过程的自动化、可靠性和高效性,从而将专注点放在开发上面。无论是通过Maven的生命周期命令进行打包,还是通过容器技术对部署过程进行优化,选择正确的工具与实践是成功实现这一目标的关键。
170 2
|
5月前
|
Java Spring 容器
解读spring5源码中实例化单例bean的调用链
解读spring5源码中实例化单例bean的调用链
|
5月前
|
安全 NoSQL Java
记录spring security执行流程
Spring Security登录授权流程简述: 1. 实现UserDetailsService,从DB加载用户信息。 2. 创建UserDetails实现类,封装用户详情。 3. 配置WebSecurityConfigurerAdapter,用BCryptPasswordEncoder加密。 4. 设定登录接口为匿名访问。 5. 注入AuthenticationManager,用其authenticate方法认证用户