解析spring循环依赖策略
循环依赖
所谓循环依赖就是多个Bean之间依赖关系形成一个闭环,例如A->B->C->...->A 这种情况,当然,最简单的循环依赖就是2个Bean之间互相依赖:A->B(A依赖B), B->A(B依赖A) 。在Spring中,如果A->B,那么在创建A的过程中会去创建B,在创建B(或B的依赖)的过程中又发现B->A,这个时候就出现了循环依赖的现象。
循环依赖的解决
spring中的循环依赖只有当
- Bean是单例,
- 通过属性注入的情况
这两个条件满足的情况下是没问题的。但是如果是通过构造器依赖,或者不是单例模式的情况下循环依赖就会抛出异常BeanCurrentlyInCreationException。下面从代码层面上解析一下为什么。
Prototype的循环依赖问题
为什么最先介绍Prototype的循环依赖呢,因为可以顺便介绍在Spring中创建Bean的流程核心流程:在AbstractFoctory的doGetBean的方法。这个方法很长,这里只写出核心逻辑,并在注解上注明了个人理解:
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException { final String beanName = transformedBeanName(name);
Object bean; //尝试获取单例对象,因为spring大部分的bean都是单例的,所以这里先尝试能否获取。
registered singletons.
Object sharedInstance = getSingleton(beanName);
//单例存在的情况下,那么beanName返回的肯定是单例类,但是这里还需要判断是不是FactoryBean
if (sharedInstance != null && args == null) {
...
//FactoryBean应该返回getObject()对象
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
} else {
//走到这里,有可能beanName是单例模式,但之前并没有实例化,或者是Prototype类型。
//首先判断不是循环依赖,这里的循环依赖指的是Prototype类型
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
} try {
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
// 如果是单例,则创建单例模式
if (mbd.isSingleton()) {
// !!!这里是解决单例循环依赖的关键,后面再分析
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
throw ex;
}
}
});
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
} else if (mbd.isPrototype()) {
// 原型模式,则创建一个新对象.
Object prototypeInstance = null;
try {
/*这里是Prototype循环依赖的问题,会记录在map中beanName,
如果在解决当前Bean的依赖过程中还依赖当前Bean,
则说明了出现了循环依赖
*/
beforePrototypeCreation(beanName);
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
//对应beforePrototypeCreation(),从map中移除
afterPrototypeCreation(beanName);
}
bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
}
...
}
} ...
return (T) bean;
}
可以看出,该流程中就考虑了Prototype的循环依赖的问题,只要在创建Prototype的Bean中出现循环依赖那么就抛出异常。但是在singleton的情况下,则通过另外的方式来解决。
Singleton的循环依赖之构造注入
在上面介绍中,出现了一个很关键的地方:
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
throw ex;
}
}
});
这个getSingleton涉及到了ObjectFactory这个接口类,这个接口的功能和FactoryBean类似,但是主要是用来解决循环依赖的。在初始化过程同决定返回的Singleton对象是。关于单例的对象的创建,又要介绍一下DefaultSingletonBeanRegistry这个类,这个类主要用来帮助创建单例模式,其中主要的属性:
/** 缓存创建的单例对象: bean名字 --> bean对象 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256); /** 缓存单例的factory,就是ObjectFactory这个东西,: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16); /** 也是缓存创建的单例对象,功能和singletonObjects不一样,
在bean构造成功之后,属性初始化之前会把对象放入到这里,
主要是用于解决属性注入的循环引用: bean name --> bean instance
*/
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16); /** 记录在创建单例对象中循环依赖的问题,还记得Prototype中又记录创建过程中依赖的map吗?
在Prototype中只要出现了循环依赖就抛出异常,而在单例中会尝试解决 */
private final Set<String> singletonsCurrentlyInCreation =
Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16));
现在回过来看getSingleton(beanName, new ObjectFactory<Object>()这个方法的实现。
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { synchronized (this.singletonObjects) {
//尝试在singletonObjects中获取
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//不存在则创建
//把当前beanName加入到singletonsCurrentlyInCreation中
beforeSingletonCreation(beanName);
try { singletonObject = singletonFactory.getObject();
}
...
finally {
...
//从singletonsCurrentlyInCreation中删除beanName
afterSingletonCreation(beanName);
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
这段逻辑是不是和Prototype中解决循环类似,这里其实就是调用了ObjectFactory的getObject()获取对象,回过头去看前面代码,ObjectFactory的getObject()方法实际调用的是createBean(beanName, mbd, args)。说到createBean(beanName, mbd, args)又不得不说AbstractAutowireCapableBeanFactory这个类,主要功能就是完成依赖注入的Bean的创建,这个类的createBean方法代码如下,注意注解说明:
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
...
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
...
} protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException { // 实例化bean
BeanWrapper instanceWrapper = null;
if (mbd.isSingleton()) {
instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
}
if (instanceWrapper == null) {
//如果没实例化则创建新的BeanWrapper
//如果是通过构造器注入,这里是一个关键点
/*
因为在A初始化的时候发现构造函数依赖B,就会去实例化B,
然后B也会运行到这段逻辑,构造函数中发现依赖A,
这个时候就会抛出循环依赖的异常
*/
instanceWrapper = createBeanInstance(beanName, mbd, args);
} //如果当前是单例,并且allowCircularReferences为true(默认就是true,除非我们不希望Spring帮我们解决)
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
/*
!!!这里很重要,把构造成功,但属性还没注入的
的bean加到singletonFactory中,这样再解决A的依赖
过程中如果依赖A,就把这个半成品返回回去。
*/
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
} Object exposedObject = bean;
try {
//自动注入属性
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
... return exposedObject;
}
注解已经注明了我的理解。就不再赘述
总结
上面代码是我一边debug一个写下的,现在写完了,根据自己的理解总结一下。
相关类说明
- AbstractBeanFactory,这个类中包含了Bean创建的主要流程,在doGetBean这个方法中包含了对Prototype循环依赖处理。逻辑很简单,出现了循环依赖则直接抛出异常
- DefaultSingletonBeanRegister 用于管理Singleton的对象的创建,以及解决循环依赖的问题,其中解决循环依赖的关键属性就是了earlySingletonObjects,他会在构造Singleton对象过程中暂时缓存构造成功,但属性还未注入的对象,这样就可以解决循环依赖的问题。
- AbstractAutowireCapableBeanFactory,自动注入的相关逻辑,包自动注入的对象的创建、初始化和注入。但如果在调用构造函数中发现了循环依赖,则抛出异常
- ObjectFactory,这个接口功能和FactoryBean类似,但是为了解决循环依赖,他决定了在获取的getSingleton()是一个完成品还是一个半成品。
思考
如果A--构造依赖->B,B--属性依赖-->A,例如:
@Component
public class BeanA {
private BeanB beanB; @Autowired
public BeanA(BeanB beanB) {
this.beanB = beanB;
}
} @Component
public class BeanB {
@Autowired
private BeanA beanA;
}
这种情况会异常吗?提示:都有可能
解析spring循环依赖策略的更多相关文章
- Spring循环依赖原理
Spring循环依赖的原理解析 1.什么是循环依赖? 我们使用Spring的时候,在一个对象中注入另一个对象,但是另外的一个对象中也包含该对象.如图: 在Student中包含了teacher的一个 ...
- Springboot源码分析之Spring循环依赖揭秘
摘要: 若你是一个有经验的程序员,那你在开发中必然碰到过这种现象:事务不生效.或许刚说到这,有的小伙伴就会大惊失色了.Spring不是解决了循环依赖问题吗,它是怎么又会发生循环依赖的呢?,接下来就让我 ...
- 这个 Spring 循环依赖的坑,90% 以上的人都不知道
1. 前言 这两天工作遇到了一个挺有意思的Spring循环依赖的问题,但是这个和以往遇到的循环依赖问题都不太一样,隐藏的相当隐蔽,网络上也很少看到有其他人遇到类似的问题.这里权且称他非典型Spring ...
- spring 循环依赖的一次 理解
前言: 在看spring 循环依赖的问题中,知道原理,网上一堆的资料有讲原理. 但今天在看代码过程中,又产生了疑问. 疑问点如下: // 疑问点: 先进行 dependon 判断String[] de ...
- 3.1 spring5源码系列--循环依赖 之 手写代码模拟spring循环依赖
本次博客的目标 1. 手写spring循环依赖的整个过程 2. spring怎么解决循环依赖 3. 为什么要二级缓存和三级缓存 4. spring有没有解决构造函数的循环依赖 5. spring有没有 ...
- spring循环依赖问题分析
新搞了一个单点登录的项目,用的cas,要把源码的cas-webapp改造成适合我们业务场景的项目,于是新加了一些spring的配置文件. 但是在项目启动时报错了,错误日志如下: 一月 , :: 下午 ...
- Spring 循环依赖
循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用CircleC,CircleC引用CircleA,则它们最终反映为一个环.此处不 ...
- Spring 循环依赖的三种方式(三级缓存解决Set循环依赖问题)
本篇文章解决以下问题: [1] . Spring循环依赖指的是什么? [2] . Spring能解决哪种情况的循环依赖?不能解决哪种情况? [3] . Spring能解决的循环依赖原理(三级缓存) 一 ...
- Spring循环依赖的解决
## Spring循环依赖的解决 ### 什么是循环依赖 循环依赖,是依赖关系形成了一个圆环.比如:A对象有一个属性B,那么这时候我们称之为A依赖B,如果这时候B对象里面有一个属性A.那么这时候A和B ...
随机推荐
- php7.0版本不再以类名命名构造函数
<?php class Car { var $color = "add"; function Car($color="green") { $this-&g ...
- 软考 程序员 下午考题 c语言 笔记
1. 数组名 是表示数组空间首地址的指针常量,程序中不允许对常量赋值. 如 int a[]; a就是数组名,表示数组控件首地址的指针常量 a = 0;是错误的,不允许对指针常量赋值 &a ...
- Python Django之路与您同行
大家好,我是TT,互联网测试行业多年,没有牛逼的背景,也没有什么可炫耀的,唯独比他人更努力,在职场打拼.遇到过的坑,走过的弯路,愿意与大家分享,分享自己的经验,少走弯路.首发于个人公众号[测试架构师] ...
- RecyclerView-------之GridView模式加载更多
随着RecyclerView的出现,Listview.GridView的使用率相对有些减少,废话不多说,先看一下效果: 代码如下: 1.自定义的RecyclerView(根据自己的需要) public ...
- 制作Docker镜像
编写DockerFile 这个DockerFile是一个制作镜像的配方,用于描述这些文件,环境,和命令.在Linux.macOS的窗口终端中,或者windows的命令提示符下,执行下面的步骤,切记如果 ...
- FreeRTOS源代码的编程标准与命名约定
编程标准 (Coding Standard) FreeRTOS 源代码遵守 MISRA (Motor Industry Software Reliability Association) 规范. 与 ...
- 记录Centos一些坑
首先说一下写这篇博客的初衷. 最近去客户现场出差,搭建一套服务端的自动构建环境. 准备支持的环境有CentOS 7.5.java8.Tomcat 8.maven3.3.9.TBA 2.1.9.4 等等 ...
- C++学习(七)入门篇——C++算数运算符
以下介绍5种C++基本运算符 +.-.×./.% 注意/为第一个数除以第二个数,结果为商的整数部分,小数部分被丢弃 %求模,两个操作数必须是整型,它生成第一个数除以第二个数的余数 如果其中一个是负数, ...
- iOS事件响应链(Responder Chain)
概述 在iOS中,视图的层级一般都是 父视图->添加各种子视图.这时候某个视图(子视图)上有个按钮,需要我们交互.但是有时候我们会发现无论如何都没有反应.这时候可能就是我们对iOS的事件传递响应 ...
- C语言基础 - 输出1-100万之间的素数
其实这个很简单 代码 网上也一大堆... //判断素数 BOOL isPrime(int num) { for (int i = 2; i <= sqrt(num); i++) { //能整除则 ...