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.yml
或 application.properties
中设置:
spring:main:allow-circular-references: true # 显式开启循环依赖
方式二:代码配置
通过 SpringApplicationBuilder
或 ConfigurableApplicationContext
设置:
// 适用于独立应用
new SpringApplicationBuilder(MyApp.class).allowCircularReferences(true).run(args);// 适用于已有上下文
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
context.setAllowCircularReferences(true);
3. 无法解决的循环依赖类型
类型 | 原因 |
---|---|
构造器注入循环依赖 | 实例化 Bean 时必须先完成依赖注入,导致无法通过提前暴露对象解决(直接抛出 BeanCurrentlyInCreationException )。 |
Prototype Bean 循环依赖 | 非单例 Bean 不缓存,每次请求都创建新对象,无法通过三级缓存机制处理。 |
4. 最佳实践
-
避免循环依赖:
- 优先通过设计模式(如引入中间类、事件驱动)解耦相互依赖。
- 使用
@Lazy
延迟加载非必要依赖:@Service public class ServiceA {@Autowired@Lazy // 延迟初始化 ServiceBprivate ServiceB serviceB; }
- 不会立即创建依赖的bean;
- 用到时才通过动态代理(cglib)进行创建。
-
替代方案:
- 方法调用替代字段注入:
@Service public class ServiceA {public ServiceB getServiceB() {return SpringUtils.getBean(ServiceB.class); // 通过工具类获取 Bean} }
- 使用
ObjectFactory
或Provider
(避免直接持有引用):@Autowired private ObjectFactory<ServiceB> serviceBFactory;public void doSomething() {ServiceB serviceB = serviceBFactory.getObject(); // 按需获取实例 }
- 方法调用替代字段注入:
5. 核心机制
- 三级缓存(仅单例 Bean):
- 一级缓存(
singletonObjects
):存放完全初始化的 Bean。 - 二级缓存(
earlySingletonObjects
):存放已实例化但未完成属性注入的 Bean。 - 三级缓存(
singletonFactories
):存放 Bean 工厂,用于提前暴露对象引用。
- 一级缓存(
- 流程示例(A 依赖 B,B 依赖 A):
- 实例化 A → 存入三级缓存。
- 注入 B → 实例化 B → 存入三级缓存。
- 注入 A → 从三级缓存获取 A 的工厂,生成早期引用 → 完成 B 的初始化。
- 完成 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 的代理对象从二级缓存移动到一级缓存;