Spring循环依赖

前言

最近发现循环依赖的源码又忘的干干净净。赶紧去撸一撸源码,记录一下。这篇文章简单说一说spring是怎么处理循环依赖的,如果你想看spring解决循环依赖又无从下手时,希望你带着这篇文章去看源码能对你有一丢小小的帮助。重点:源码一定要自己去撸,哪怕带着别人的理解,或者已经知道了怎么回事,也要亲自去撸一下源码。

注入循环依赖。

场景:beanA使用@Autowired注解注入beanB,beanB使用@Autowired注解注入beanA,当然,也可以是个大循环,只要是个闭环就行。

流程:

  1. beanA进入doGetBean()方法
  2. 尝试从缓存里拿beanA,没拿到
  3. 多例集合检查,因为是默认是单例,检查通过
  4. 创建beanA,创建前检查,检查把beanA放到正在创建的单例set集合里,添加成功
  5. 调用beanA构造方法实例化
  6. 收集beanA的属性(这里收集到了beanB)
  7. 添加beanA到三级缓存
  8. 注入收集的属性,注入beanB,触发beanB的创建
  9. beanB进入doGetBean()方法
  10. 尝试从缓存里拿beanB,没拿到
  11. 多例集合检查,因为是默认是单例,检查通过
  12. 创建beanB,创建前检查,检查把beanB放到正在创建的单例set集合里,添加成功,此时set集合里有beanA和beanB
  13. 调用beanB构造方法实例化
  14. 收集beanB的属性(这里收集到了beanA)
  15. 添加beanB到三级缓存
  16. 注入收集的属性,注入beanA,触发beanA的创建
  17. beanA进入doGetBean()方法
  18. 尝试从缓存里拿beanA,一级缓存没找到,找二级缓存,二级缓存没找到,找三级缓存,找到了。
  19. 通过三级缓存实例化beanA,删除三级缓存,放到二级缓存里去。
  20. beanB的属性注入完成
  21. beanB创建完成
  22. 返回到beanA的创建栈帧,beanA的属性注入完成
  23. beanA和beanB创建完成

构造方法循环依赖

场景:beanA在构造方法里传入beanB参数,beanB在构造方法里传入beanA参数,当然,也可以是个大循环,只要是个闭环就行。

流程:

  1. beanA进入doGetBean()方法
  2. 尝试从缓存里拿beanA,没拿到
  3. 多例集合检查,因为是默认是单例,检查通过
  4. 创建beanA,创建前检查,检查把beanA放到正在创建的set集合里,添加成功
  5. 调用beanA构造方法实例化
  6. 实例化之前spring会准备构造方法的参数,去容器里找beanB
  7. 没找到beanB,触发beanB的创建
  8. beanB进入doGetBean()方法
  9. 尝试从缓存里拿beanB,没拿到
  10. 多例集合检查,因为是默认是单例,检查通过
  11. 创建beanB,创建前检查,检查把beanB放到正在创建的单例set集合里,添加成功,此时set集合里有beanA和beanB
  12. 调用beanB构造方法实例化
  13. 实例化之前spring会准备构造方法的参数,去容器里找beanA
  14. 没找到beanA,触发beanA的创建
  15. beanA进入doGetBean()方法
  16. 尝试从缓存里拿beanA,没找到
  17. 创建beanA,创建前检查,检查把beanA放到正在创建的set集合里,添加失败
  18. 添加失败就抛异常,流程结束

多例循环依赖

场景:beanA在构造方法里传入beanB参数,beanB在构造方法里传入beanA参数,当然,也可以是个大循环,只要是个闭环就行。但是bean的scope属性都是prototype

流程:

  1. beanA进入doGetBean()方法
  2. 尝试从缓存里拿beanA,没拿到,一级缓存没拿到,多例不允许去二级三级缓存里找
  3. 多例集合检查,这时候啥也没有,检查通过
  4. 创建beanA,创建前检查,检查把beanA放到正在创建的多例set集合里,添加成功
  5. 调用beanA构造方法实例化
  6. 收集beanA的属性(这里收集到了beanB)
  7. 多例,不添加beanA到三级缓存
  8. 注入收集的属性,注入beanB,触发beanB的创建
  9. beanB进入doGetBean()方法
  10. 尝试从缓存里拿beanB,没拿到,一级缓存没拿到,多例不允许去二级三级缓存里找
  11. 多例集合检查,这时候里面只有beanA,检查通过
  12. 创建beanB,创建前检查,检查把beanB放到正在创建的多例set集合里,添加成功,此时set集合里有beanA和beanB
  13. 调用beanB构造方法实例化
  14. 收集beanB的属性(这里收集到了beanA)
  15. 多例,不添加beanB到三级缓存
  16. 注入收集的属性,注入beanA,触发beanA的创建
  17. beanA进入doGetBean()方法
  18. 尝试从缓存里拿beanA,没拿到,一级缓存没拿到,多例不允许去二级三级缓存里找
  19. 多例集合检查,检查失败,发现已经存在beanA,抛异常,流程结束。

问题

这里有个问题,为何是三级缓存,不是二级缓存。

我们看一下三级缓存都存的是什么。

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    // 一级缓存
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

    // 三级缓存
    /** Cache of singleton factories: bean name to ObjectFactory. */
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    // 二级缓存
    /** Cache of early singleton objects: bean name to bean instance. */
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

}

一级缓存和二级缓存,存的都是bean,区别在于一级缓存的bean,是初始化后的bean,二级缓存存的是实例化,但还没有完成初始化的bean

三级缓存里,存的是ObjectFactory,这里可以引申出一个问题,ObjectFactory和FactoryBean有什么区别。有兴趣的小伙伴可以自行研究一下。

三级缓存其实主要是解决对象的引用类型的。

这里想象一个场景,一般的类循环依赖,其实二级缓存就已经能实现功能,那么假如有aop功能的类循环依赖呢?我们知道aop代理后的类,在ioc容器里指向的其实是一个代理对象。我们在aop功能的类循环依赖的时候,总要有一个地方去调用aop的createProxy()方法。

三级缓存就是干这个事情的,我们走一下源码。

// 放入三级缓存的时候,放入的是一个ObjectFactory对象,这里使用的是函数式接口
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

根据上面的代码,当spring从三级缓存里取对象的时候,一定会调用SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference()方法

目前这个地方真正的实现只有一个:

public Object getEarlyBeanReference(Object bean, String beanName) {
        Object cacheKey = this.getCacheKey(bean.getClass(), beanName);
        this.earlyProxyReferences.put(cacheKey, bean);
        return this.wrapIfNecessary(bean, beanName, cacheKey);
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    } else if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    } else if (!this.isInfrastructureClass(bean.getClass()) && !this.shouldSkip(bean.getClass(), beanName)) {
    // 这里去获取类的aop的扩展点
        Object[] specificInterceptors = this.getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, (TargetSource)null);
        // 这里DO_NOT_PROXY就是个null,如果上面代码能获取到aop的扩展点,那这个判断就成立,就走aop的createProxy 
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            Object proxy = this.createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        } else {
        // 走这里代表specificInterceptors是个null,也就是我们普通的没有经过aop代理的类,直接返回bean
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }
    } else {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }
}

问题已经很明白了,三级缓存是为了解决aop代理的类循环依赖的问题。

  • Spring创建Bean的时候把ObjectFactory放入三级缓存,这里假设是bean-a,bean-a依赖了bean-b
  • 存在循环依赖,bean-b依赖了bean-a,去三级缓存里找bean-a,然后执行SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法
    • 如果bean-a是个普通类,那么走完getEarlyBeanReference()方法,放入二级缓存的是个普通的bean-a对象
    • 如果bean-a是个代理类,那么走完getEarlyBeanReference()方法,放入二级缓存的是个bean-a的代理对象

aop的处理

aop处理其实是有两种处理方式的,一种是正常处理。一种是前置处理。

  • 正常处理 (在类的初始化的时候,走BeanPostProcessor的扩展点去创建代理类)
    • 走BeanPostProcessor
    • 走AspectJAwareAdvisorAutoProxyCreator
    • 走postProcessAfterInitialization
  • 前置处理 (在存在aop代理的类存在循环依赖的时候)
    • 走BeanPostProcessor
    • 走AspectJAwareAdvisorAutoProxyCreator
    • 走getEarlyBeanReference

看到这里,知道为什么我说三级缓存是为了解决aop代理的类存在循环依赖的场景了吧,目前这里只有这一个扩展点。

总结

spring目前只允许单例注入的循环依赖存在,建议自己去撸一撸源码。我只提到了bean创建时关于循环依赖的细节,跟循环依赖无关的我没有提到,这不代表bean的整个创建流程。虽然我写的很简陋,但是足够帮助你去自己走一遍源码。

附:

  1. doGetBean()在AbstractBeanFactory类里。
  2. 去缓存里拿是类DefaultSingletonBeanRegistry的getSingleton()方法。
  3. spring 2.6以后,默认关闭了循环依赖,如果开启的话,需要在配置文件里进行配置。
  4. 如果你坚持要使用构造方式的循环依赖,可以加上@Lazy注解,让bean懒加载。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容