【Spring源码】简洁明了说透循环依赖

98 阅读8分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天
Spring Bean生命周期,从入门到进阶:
【Spring Bean生命周期】小白也能看懂的入门篇
【Spring Bean生命周期】高手如何解读源码(一) - 资源的扫描和注册
【Spring Bean生命周期】高手如何解读源码(二) - Bean的生命周期
【Spring Bean生命周期】聊透扩展点的流程及应用

1. 什么是循环依赖

代码示例

image.png

2. 在Spring中循环依赖会带来什么问题

本身没问题,但在Spring中会出现问题。看过我们前面文章的小伙伴应该知道,Spring的生命周期是这样滴

image.png

当存在循环依赖时,代码的调用顺序是这样滴

image.png 红色线表示运行流程,代码调用成环了。。等栈溢出吧

3. Spring如何解决循环依赖

名称含义
singletonObjects一级缓存存的是成品对象,实例化和初始化都完成了,我们的应用中使用的对象就是一级缓存中的
earlySingletonObjects二级缓存存的是半成品,还未完成生命周期,早期占位,用来解决对象创建过程中的循环依赖问题。
singletonFactories三级缓存存的是lambda表达式,用于生产代理对象。

解决思路提前曝光+引用机制,将实例对象提前放到缓存,等到使用的时候先去缓存找,避免重复被实例化。

3.1 如果只有一级缓存

image.png 流程:
A进行实例化
->A放入一级缓存中
->A的属性注入,依赖B且缓存中不存在B,触发B实例化
->B放入一级缓存中
->B的属性注入,依赖C且缓存中不存在C,触发C实例化
->C放入一级缓存中
->C的属性注入,依赖A且缓存中A存在A,从缓存中拿到A
->C的初始化
->B的初始化
->A的初始化
问题: 这种基本流程是通的,但是如果在整个流程进行中,有另一个线程要来取A,那么有可能拿到的只是一个属性都为null的半成品A,这样就会有问题。如何解决这个问题呢?

3.2 如果只有二级缓存

image.png 流程:
A进行实例化
->A放入二级缓存中
->A的属性注入,依赖B且缓存中(从第一层到第二层缓存中依次获取)不存在B,触发B实例化
->B放入二级缓存中
->B的属性注入,依赖C且缓存中不存在C,触发C实例化
->C放入二级缓存中
->C的属性注入,依赖A且缓存中A存在A,从缓存中拿到A
->C的初始化
->C放入一级缓存,且移除二级缓存中的C'
->B的初始化
->B放入一级缓存,且移除二级缓存中的B'
->A的初始化
->A放入一级缓存,且移除二级缓存中的A'
问题:
二级缓存很好的解决了半成品的问题,但是Spring再提供强大扩展能力的同时,也给自己带来了不可预知。比如:如果在初始化完成之后,我通过BeanPostProcessorr修改了生成的对象。比如:AOP会在初始化完成后生成一个新的代理对象,则一级缓存中的对象并不是我们期望的代理对象,就会导致我们的代理逻辑失效,那如何解决这个问题呢?

3.3 三级缓存

解决上面问题的思路仍然是提前暴露,即把提前暴露代理对象的引用地址。为了职责清晰,我们引入三级缓存专门用来存放需要增强的Bean(即实现了SmartInstantiationAwareBeanPostProcessor接口的Bean)。 image.png 流程:
A进行实例化
->A放入三级缓存中
->A的属性注入,依赖B且缓存中(从第一层到第三层缓存中依次获取)不存在B,触发B实例化
->B放入三级缓存中
->B的属性注入,依赖C且缓存中不存在C,触发C实例化
->C放入三级缓存中
->C的属性注入,依赖A且缓存中A存在A,从缓存中拿到A
->C放入二级缓存,且移除三级缓存中的C' ->C的初始化
->C放入一级缓存,且移除二级缓存中的C' ->B放入二级缓存,且移除三级缓存中的B'
->B的初始化
->B放入一级缓存,且移除二级缓存中的B' ->A放入二级缓存,且移除三级缓存中的A' ->A的初始化
->A放入一级缓存,且移除二级缓存中的A'

看下doCreateBean代码:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args) {
        ...

        if (instanceWrapper == null) {
        // 创建A_Origin对象,此时,属性什么的全是null,可以理解为,只是new了,field还没设置
        instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
final Object bean = (instanceWrapper != null ? instanceWrapper.getWrappedInstance() : null);
        Class beanType = (instanceWrapper != null ? instanceWrapper.getWrappedClass() : null);

        ...

        // 如果该bean是单例且允许Spring配置允许循环依赖,且当前bean确实在创建中
        boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
        isSingletonCurrentlyInCreation(beanName));
        if (earlySingletonExposure) {
        ...
        // 添加到第三级缓存;加进去的,只是个factory,只有循环依赖的时候,才会发挥作用
        addSingletonFactory(beanName, new ObjectFactory() {
public Object getObject() throws BeansException {
        return getEarlyBeanReference(beanName, mbd, bean);
        }
        });
        }

        // 把A_Origin,存到exposedObject
        Object exposedObject = bean;
        try {
        // 填充属性;循环依赖情况下,A/B循环依赖。当前为A_Origin,那么此时填充A的属性的时候,会去:new B;
        // 并且因为三级缓存机制,B中引用的A是A_Proxy。然后B初始化完成,逻辑返回,继续A_Origin的初始化,A_Origin引用了B。
        populateBean(beanName, mbd, instanceWrapper);
        if (exposedObject != null) {
        // 对A_Origin进行后置处理,此时调用aop后置处理器的postProcessAfterInitialization;
        // 这里底层是有可能调用wrapIfNecessary获取到代理对象。
        // 不过Spring为了防止wrapIfNecessary被重复调用,从而生成不同的A_Proxy对象,
        // 所以对于一个bean而言,Spring会记下已经调用了wrapIfNecessary的beanName,保证wrapIfNecessary只会被调用一次。
        // 因为在B的初始化中,A的wrapIfNecessary已经被调用了,所以在这里,wrapIfNecessary不会被调用
        // 所以此处返回的是A_Origin
        exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
        }

        ...

        if (earlySingletonExposure) {
        // 去三级缓存里获取A,拿到的是A_Proxy
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
        // 此时exposedObject=A_Origin, bean=A_Origin, earlySingletonReference=A_Proxy
        // 所以下面这个条件是满足的,所以,exposedObject,最终被替换为A_Proxy:
        if (exposedObject == bean) {
        exposedObject = earlySingletonReference;
        }
        ...
        }
        }

        ...

        return exposedObject;
        }

问题:也许有人会问,就算只使用两级缓存,我如果在 A 实例化后,紧接着就调用 getEarlyBeanReference () 方法去创建切面,然后将生成的 A_Proxy 放入二级缓存行不行?这不是又可以避免代理对象的问题,又只需要两级缓存吗?

答案是:可以,但没有必要

因为 Spring 中循环依赖出现场景很少,我们没有必要为了解决系统中那 1% 可能出现的循环依赖问题,而让 99% 的 bean 在创建时都去调用 getEarlyBeanReference () 走上这么一圈。大部分 bean 调用 getEarlyBeanReference (),只会徒增判断逻辑,而没有实质的作用,他们既没有切面,也没有配置相关的 BeanPostProcessor 类。

使用三级缓存,就可以让确实有循环依赖场景的 bean 才会去调用 getEarlyBeanReference ()。因为只有有循环依赖场景的 bean,才会用到二三级缓存。

4. 其他类型的循环依赖

这个不在展开,引用(# 哪些循环依赖问题Spring解决不了?)

场景一: prototype 类型的循环依赖(无法解决)

描述:  A --> B --> A,且 A,B 都是 scope=prototype ,原因:

A 实例创建后,populateBean 时,会触发 B 的加载。   
B 实例创建后,populateBean 时,会触发 A 的加载。由于 A 的 scope=prototype,从缓存中获取不到 A,要创建一个全新的 A。   
这样,就会进入一个死循环。Spring 肯定是解决不了这种情况下的循环依赖的。所以,提前进行了 check,并抛出了异常。
@Service
@Scope("prototype")
public class A {
    @Autowired
    private B b;
}

@Service
@Scope("prototype")
public class B {
    @Autowired
    private A a;
}

场景二: constructor 注入的循环依赖(无法解决)

描述:  A --> B --> A,且 都是通过构造函数依赖的

A 实例在创建时(createBeanInstance),由于是构造注入,这时会触发 B 的加载。  
B 实例在创建时(createBeanInstance),又会触发 A 的加载,此时,A 还没有添加到三级缓存中,所以就会创建一个全新的 A
@Service
public class A {
    private B b;
    
    public A(B b) {
        this.b=b;
    }
}

@Service
public class B {
    private A a;
    
    public B(A a) {
        this.a=a;
    }
}

场景三:@Async 增强的 Bean 的循环依赖(无法解决)

描述: A --> B --> A, 且 A 是被 @Async 标记的类

proxy 的创建是在 initializeBean 的时候,通过 BeanPostProcessor 处理的。
A 在 createBeanInstance 之后,添加到三级缓存。populateBean 时触发 B 的加载。
B 在 createBeanInstance 之后,添加到三级缓存。populateBean 时触发 A 的加载,这时,三级缓存中有 A,那么通过三级缓存 ObjectFactory#get() 可以获取到 bean 的早期引用。

普通的 AOP 代理都是通过 AbstractAutoProxyCreator 来生成代理类的,AbstractAutoProxyCreator 实现了 SmartInstantiationAwareBeanPostProcessor。
而 @Async 标记的类是通过 AbstractAdvisingBeanPostProcessor 来生成代理的,AbstractAdvisingBeanPostProcessor 没有实现 SmartInstantiationAwareBeanPostProcessor。

public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor {
    .......
}
@Service
public class A {
    @Autowired
    private B b;
    
    @Async
    public void m1(){
    }
}

@Service
public class B {
    @Autowired
    private A a;
}