Spring源码之循环依赖详解
文章目录
前言
IOC是Spring的重要特性,但在依赖注入过程中,如果不注意使用的话就会发生循环依赖现象。这篇文章通过跟踪Spring实例化bean的过程,来分析循环依赖产生的原因以及如何解决循环依赖的。
一、单例bean实例化的过程
- getSingleton
- doCreateBean
2.1. createBeanInstance
2.2. applyMergedBeanDefinitionPostProcessors
2.3. addSingletonFactory
2.4. populateBean - addSingleton
1. getSingleton
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//根据beanName从缓存中拿实例
//先从一级缓存拿
Object singletonObject = this.singletonObjects.get(beanName);
//如果bean还正在创建,还没创建完成,其实就是堆内存有了,属性还没有DI依赖注入
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
//从二级缓存中拿
singletonObject = this.earlySingletonObjects.get(beanName);
//如果还拿不到,并且允许bean提前暴露
if (singletonObject == null && allowEarlyReference) {
//从三级缓存中拿到对象工厂
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
//从工厂中拿到对象
singletonObject = singletonFactory.getObject();
//升级到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
//删除三级缓存
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
Spring提供了三个map用来存放不同状态的bean,也就是我们常说的三级缓存。
- singletonObjects(单例池,存放实例化完成的bean)
- earlySingletonObjects(映射bean的早期引用,里面存放实例化不完整的bean)
- singletonFactories(bean的原始工厂)
2. doCreateBean
2.1. createBeanInstance
//寻找当前正在实例化的bean中有@Autowired注解的构造函数
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
//如果ctors不为空,就说明构造函数上有@Autowired注解,或者只有一个有参构造函数
return autowireConstructor(beanName, mbd, ctors, args);
}
//否则调用bean的无参构造函数进行instantiate
return instantiateBean(beanName, mbd);
2.2. applyMergedBeanDefinitionPostProcessors
//收集bean中的注解,比如@Autowired,@Value,@Resource等
protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof MergedBeanDefinitionPostProcessor) {
MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) bp;
bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName);
}
}
}
2.3. addSingletonFactory
//是否允许bean提前暴露(单例、允许循环依赖、正在被创建)
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isTraceEnabled()) {
logger.trace("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
//添加三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
2.4. populateBean
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
//依赖注入过程,@Autowired的支持
PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
if (filteredPds == null) {
filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
}
//老版本用这个完成依赖注入过程,@Autowired的支持
pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
return;
}
}
pvs = pvsToUse;
}
}
二、循环依赖产生的原因
在bean实例化的过程中,也就是在createBeanInstance
方法,Spring会调用bean的构造方法进行instantiated。看过我前面文章的就会清楚,如果调用的是有参函数,参数是一个引用类型,那么Spring会先去实例化参数,如果这里不注意的话就很容易产生循环依赖问题。
举个例子:假如有2个bean,A和B,构造函数如下:public A(B b)
,public B(A a)
A实例化时需要B实例依赖注入到A中,这样在A还没有实例化完成的时候就会去实例化B,当B实例化时需要A实例依赖注入到B中,这样在B还没有实例化完成的情况下就会去实例化A,从而形成一个闭合的循环,类似于死锁。
如果要实例化的A的构造方法是有参的B,那么会调用autowireConstructor方法,因为构造方法里面有参数,所以先会对参数进行处理,这样就会调用参数B的getBean方法,这样就会触发B的createBeanInstance方法,因为B的构造函数里面需要依赖注入A,因为A还没有实例化完成,就会继续调用A的createBeanInstance方法,进而形成循环,造成内存溢出。
三、循环依赖的解决办法
如果bean在进行instantiated的时候,只调用bean的无参构造,就不会产生循环依赖问题。如果我们一定要完成A和B之间的相互注入,可以采用属性注入的方式。
在2.2步和2.4步之间,Spring会将bean放入到三级缓存,也就是bean原始工厂中。这样在实例化A时,属性B进行依赖注入,就会去实例化B,B中的A属性进行依赖注入就会去实例化A,但在实例化A之前会先去缓存中获取,因为之前在实例化A时,已经添加了三级缓存,所以这里可以直接从缓存中拿到A的实例,那么B的实例化完成,这样A就能拿到B的实例完成依赖注入,A的实例化完成。
实例化完成之后加入到一级缓存单例池中,同时删除二级和三级缓存。
从上面的流程中可以看出,三级缓存只是作为一个过渡使用,在bean实例化完成之后就不存在了。
如果是多例的情况,Spring是不支持循环依赖的,其实也好理解,原型模式不存在缓存,每次获取bean都是创建新的,这样bean之间存在相互引用就会OOM。
//如果是原型模式,循环依赖则会报异常
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
思考
我们来看一看三级缓存具体是什么
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
可以看出又是一个BeanPostProcessor的应用,以工厂的方式对exposedObject
反复修饰,这里我们可以对其进行扩展,只要实现SmartInstantiationAwareBeanPostProcessor
接口,重写getEarlyBeanReference
方法。由此可见Spring提供给我们的高扩展性,在我们平时的代码设计中,是不是也应该考虑到高扩展性呢。
本文地址:https://blog.csdn.net/qq_27023083/article/details/112638020
上一篇: Java基础语法---类和对象
下一篇: 数字化转型还是数字优化,是个艰难选择
推荐阅读