当前位置: 首页 > 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{@AutowiredB b;
}
class B{@AutowiredA 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  // 延迟初始化 ServiceBprivate 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{@AutowiredB b;@AutowiredC c;
}class B{@AutowiredA a;
}class C{@AutowiredA 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 的代理对象从二级缓存移动到一级缓存;


http://www.mrgr.cn/news/94535.html

相关文章:

  • k8s集群-kubeadm init
  • 压敏电阻结构特点及选型指南
  • 【图论】并查集的学习和使用
  • 卫语句优化多层if else嵌套
  • 计算机视觉cv2入门之边缘检测
  • Python Matplotlib面试题精选及参考答案
  • Python精进系列:隐藏能力之魔术方法
  • 315周六复盘(118)本周回顾
  • 入门基础项目-前端Vue_02
  • UE4-UE5虚幻引擎,前置学习一--Console日志输出经常崩溃,有什么好的解决办法
  • MySQL开发陷阱与最佳实践:第1章:MySQL开发基础概述-1.2 MySQL开发环境搭建
  • 链表·简单归并
  • 【技术支持】记一次mac电脑换行符差异问题
  • 精通游戏测试笔记(持续更新)
  • 【云原生技术】容器技术的发展史
  • 高频面试题(含笔试高频算法整理)基本总结回顾48
  • 高频面试题(含笔试高频算法整理)基本总结回顾43
  • 【Linux内核系列】:文件系统收尾以及软硬链接详解
  • AI Agent 时代开幕-Manus AI与OpenAI Agent SDK掀起新风暴
  • sentinel限流算法