当前位置: 首页 > news >正文

spring 创建单例 Bean 源码分析

一、创建单例Bean

1、创建单例 Bean

通过方法getBean()来创建单例bean。

代码入口:
org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons

spring boot version:2.6.13

在这里插入图片描述
在这里插入图片描述


org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean

在这里插入图片描述

上图步骤3中的依赖是指通过 @DependsOn 注解或 XML 的 depends-on 属性配置;
若存在循环依赖(如 A 依赖 B,B 又依赖 A),启动时直接抛出异常,阻止容器启动


2、三级缓存

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#singletonObjects

在这里插入图片描述

3、获取单例Bean

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, org.springframework.beans.factory.ObjectFactory<?>)

在这里插入图片描述

注意方法参数ObjectFactory<?>,用于在单例池中找不到bean时,会调用工厂进行生产bean。工厂具体逻辑在图片右下角。

生产完成后通过方法addSingleton()将单例 Bean 添加进单例池中。

在这里插入图片描述

4、创建单例Bean

在这里插入图片描述


5、生产单例bean具体步骤

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

在这里插入图片描述


5.1、bean 属性填充具体步骤

在这里插入图片描述


5.2、初始化 bean 具体步骤

在这里插入图片描述

至此,创建bean就完成了。


5.3、解决循环依赖

如果创建过程中遇到了循环依赖时,我们需要再看下具体细节,即小节 5.1(bean 属性填充具体步骤)中填充Bean属性的具体逻辑。

在这里插入图片描述

以按照bean名字注入属性为例,通过方法 getBean() 来获取需要注入的bean。


在这里插入图片描述


org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton(java.lang.String, boolean)

在这里插入图片描述


实际案例如下:

class A{
    @Autowired
    B b;
}
class B{
    @Autowired
    A a;
}

1、创建 a =》a 放入单例工厂singletonFactories =》 填充 a 的属性 b;
2、创建 b =》b 放入单例工厂singletonFactories =》 填充 b 的属性 a;
3、将 a 从单例工厂singletonFactories移动到二级缓存 earlySingletonObjects,注入b;
4、b 创建完成 =》将 b 从单例工厂singletonFactories 移动到单例池singletonObjects中;
5、从单例池singletonObjects获取 b 注入 a;
6、a 创建完成 =》将 a 从二级缓存 earlySingletonObjects移动到单例池singletonObjects中。


二、 循环依赖

1. 默认行为变化

Spring Boot 版本循环依赖默认状态说明
<2.6.x允许默认支持单例 Bean 的 setter 注入循环依赖(通过三级缓存机制)。
≥2.6.x禁止默认关闭循环依赖,启动时若检测到循环依赖直接报错,需手动配置开启。

2. 配置允许循环依赖

方式一:全局配置文件(推荐)

application.ymlapplication.properties 中设置:

spring:
  main:
    allow-circular-references: true  # 显式开启循环依赖

方式二:代码配置

通过 SpringApplicationBuilderConfigurableApplicationContext 设置:

// 适用于独立应用
new SpringApplicationBuilder(MyApp.class)
    .allowCircularReferences(true)
    .run(args);

// 适用于已有上下文
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
context.setAllowCircularReferences(true);

3. 无法解决的循环依赖类型

类型原因
构造器注入循环依赖实例化 Bean 时必须先完成依赖注入,导致无法通过提前暴露对象解决(直接抛出 BeanCurrentlyInCreationException)。
Prototype Bean 循环依赖非单例 Bean 不缓存,每次请求都创建新对象,无法通过三级缓存机制处理。

4. 最佳实践

  1. 避免循环依赖

    • 优先通过设计模式(如引入中间类、事件驱动)解耦相互依赖。
    • 使用 @Lazy 延迟加载非必要依赖:
      @Service
      public class ServiceA {
          @Autowired
          @Lazy  // 延迟初始化 ServiceB
          private ServiceB serviceB;
      }
      
      • 不会立即创建依赖的bean;
      • 用到时才通过动态代理(cglib)进行创建。
  2. 替代方案

    • 方法调用替代字段注入
      @Service
      public class ServiceA {
          public ServiceB getServiceB() {
              return SpringUtils.getBean(ServiceB.class); // 通过工具类获取 Bean
          }
      }
      
    • 使用 ObjectFactoryProvider(避免直接持有引用):
      @Autowired
      private ObjectFactory<ServiceB> serviceBFactory;
      
      public void doSomething() {
          ServiceB serviceB = serviceBFactory.getObject(); // 按需获取实例
      }
      

5. 核心机制

  • 三级缓存(仅单例 Bean):
    • 一级缓存(singletonObjects:存放完全初始化的 Bean。
    • 二级缓存(earlySingletonObjects:存放已实例化但未完成属性注入的 Bean。
    • 三级缓存(singletonFactories:存放 Bean 工厂,用于提前暴露对象引用。
  • 流程示例(A 依赖 B,B 依赖 A):
    1. 实例化 A → 存入三级缓存。
    2. 注入 B → 实例化 B → 存入三级缓存。
    3. 注入 A → 从三级缓存获取 A 的工厂,生成早期引用 → 完成 B 的初始化。
    4. 完成 A 的初始化。

6. 版本兼容建议

  • 升级到 Spring Boot ≥2.6.x
    建议逐步消除现有循环依赖,而非依赖 allow-circular-references 配置。
  • 遗留项目适配
    若短期内无法重构,临时开启循环依赖并记录技术债务,后续优化。

总结:Spring Boot 2.6+ 默认禁止循环依赖以提升代码质量,开发者应优先通过设计消除依赖闭环。若需临时兼容,可通过配置文件或代码显式开启,但需明确此操作仅为过渡方案。

三、引申思考

1、三级缓存作用

  • 一级缓存:存储完整的Bean
  • 二级缓存:避免多重循环依赖的情况重复创建动态代理。
  • 三级缓存:
    • 缓存是函数接口:把Bean的实例和Bean名字传进去
    • 不会立即调用
    • 会在ABA(第二次getBean(A)才会去调用三级缓存(如果实现了aop才会创建动态代理,如果没有实现依然返回的Bean的实例))
    • 放入二级缓存(避免重复创建)

2、为啥需要三级缓存解决循环依赖?

  • 一级缓存可以解决死循环的问题;但在并发情况下会获取到尚未初始化完的Bean。

  • 二级缓存可以解决循环依赖;但在AOP动态代理时,循环依赖会导致重复创建AOP代理。

  • 三级缓存保证创建AOP动态代理一次。

    • 没有循环依赖时,AOP动态代理在bean初始化后生成。
    • 有循环依赖时,AOP动态代理在实例化后生成。

实际案例如下:

@Transactional
class A{
    @Autowired
    B b;
    
    @Autowired
    C c;
}

class B{
    @Autowired
    A a;
}

class C{
    @Autowired
    A a;
}

1、创建a =》放入三级缓存 =》填充属性 b;
2、创建b =》放入三级缓存 =》填充属性 a;
3、三级缓存获取 a 的AOP 代理对象放入二级缓存,删除三级缓存(注意,若不移动,后续其它依赖 a 的 bean 会再次生成 a 的代理, 导致 a 的代理对象存在多个,与我们最开始的单例矛盾);
4、将 a 的AOP代理对象 注入b ;
5、将 b 从三级缓存移到一级缓存;
6、将 b 注入 a;
7、a 开始 填充属性 c;
8、 创建c =》放入三级缓存 =》填充属性 a;
9、二级缓存获取 a 的AOP代理对象注入 c;
10、将 c 从三级缓存移到一级缓存;
11、 将 c 注入 a;
12、将 a 的代理对象从二级缓存移动到一级缓存;

相关文章:

  • itsdangerous加解密源码分析|BUG汇总
  • 大语言模型入门文献推荐
  • 每日Attention学习28——Strip Pooling
  • 【Golang】第二弹-----变量、基本数据类型、标识符
  • 上传本地项目到GitHub
  • 守护中国软件供应链安全,未名湖畔的筑梦人
  • Adobe Premiere Pro2023配置要求
  • 【Function】使用托管身份调用Function App触发器,以增强安全性
  • 深入解析 TensorFlow 兼容性问题及构建输出文件结构*
  • 操作系统八股文整理(一)
  • PyTorch 深度学习实战(11):强化学习与深度 Q 网络(DQN)
  • 【C++基础十】泛型编程(模板初阶)
  • Windows 环境图形化安装 Oracle 23ai
  • spring声明式事务原理02-调用第1层@Transactional方法-按需创建事务createTransactionIfNecessary
  • 深入解析“Off-the-Shelf”——从产品到AI模型的通用概念
  • 视觉定位项目中可以任意修改拍照点位吗?
  • ElementUI 表格中插入图片缩略图,鼠标悬停显示大图
  • 图像处理篇---图像预处理
  • 【宠粉赠书】极速探索 HarmonyOS NEXT:国产操作系统的未来之光
  • tongweb信创项目线上业务添堵问题排查
  • 出口网站有哪些/seo百度网站排名软件
  • 阿里巴巴吧国际网站怎么做/大数据分析师
  • 电子商务网站建设的概要设计/合肥网络推广软件系统
  • b2b网站建设深圳/百度入口网页版
  • 贵阳网站建设是什么/百度点击优化
  • 网站建设上海网站建设/在线培训课程