前言
最近发现循环依赖的源码又忘的干干净净。赶紧去撸一撸源码,记录一下。这篇文章简单说一说spring是怎么处理循环依赖的,如果你想看spring解决循环依赖又无从下手时,希望你带着这篇文章去看源码能对你有一丢小小的帮助。重点:源码一定要自己去撸,哪怕带着别人的理解,或者已经知道了怎么回事,也要亲自去撸一下源码。
注入循环依赖。
场景:beanA使用@Autowired注解注入beanB,beanB使用@Autowired注解注入beanA,当然,也可以是个大循环,只要是个闭环就行。
流程:
- beanA进入doGetBean()方法
- 尝试从缓存里拿beanA,没拿到
- 多例集合检查,因为是默认是单例,检查通过
- 创建beanA,创建前检查,检查把beanA放到正在创建的单例set集合里,添加成功
- 调用beanA构造方法实例化
- 收集beanA的属性(这里收集到了beanB)
- 添加beanA到三级缓存
- 注入收集的属性,注入beanB,触发beanB的创建
- beanB进入doGetBean()方法
- 尝试从缓存里拿beanB,没拿到
- 多例集合检查,因为是默认是单例,检查通过
- 创建beanB,创建前检查,检查把beanB放到正在创建的单例set集合里,添加成功,此时set集合里有beanA和beanB
- 调用beanB构造方法实例化
- 收集beanB的属性(这里收集到了beanA)
- 添加beanB到三级缓存
- 注入收集的属性,注入beanA,触发beanA的创建
- beanA进入doGetBean()方法
- 尝试从缓存里拿beanA,一级缓存没找到,找二级缓存,二级缓存没找到,找三级缓存,找到了。
- 通过三级缓存实例化beanA,删除三级缓存,放到二级缓存里去。
- beanB的属性注入完成
- beanB创建完成
- 返回到beanA的创建栈帧,beanA的属性注入完成
- beanA和beanB创建完成
构造方法循环依赖
场景:beanA在构造方法里传入beanB参数,beanB在构造方法里传入beanA参数,当然,也可以是个大循环,只要是个闭环就行。
流程:
- beanA进入doGetBean()方法
- 尝试从缓存里拿beanA,没拿到
- 多例集合检查,因为是默认是单例,检查通过
- 创建beanA,创建前检查,检查把beanA放到正在创建的set集合里,添加成功
- 调用beanA构造方法实例化
- 实例化之前spring会准备构造方法的参数,去容器里找beanB
- 没找到beanB,触发beanB的创建
- beanB进入doGetBean()方法
- 尝试从缓存里拿beanB,没拿到
- 多例集合检查,因为是默认是单例,检查通过
- 创建beanB,创建前检查,检查把beanB放到正在创建的单例set集合里,添加成功,此时set集合里有beanA和beanB
- 调用beanB构造方法实例化
- 实例化之前spring会准备构造方法的参数,去容器里找beanA
- 没找到beanA,触发beanA的创建
- beanA进入doGetBean()方法
- 尝试从缓存里拿beanA,没找到
- 创建beanA,创建前检查,检查把beanA放到正在创建的set集合里,添加失败
- 添加失败就抛异常,流程结束
多例循环依赖
场景:beanA在构造方法里传入beanB参数,beanB在构造方法里传入beanA参数,当然,也可以是个大循环,只要是个闭环就行。但是bean的scope属性都是prototype
流程:
- beanA进入doGetBean()方法
- 尝试从缓存里拿beanA,没拿到,一级缓存没拿到,多例不允许去二级三级缓存里找
- 多例集合检查,这时候啥也没有,检查通过
- 创建beanA,创建前检查,检查把beanA放到正在创建的多例set集合里,添加成功
- 调用beanA构造方法实例化
- 收集beanA的属性(这里收集到了beanB)
- 多例,不添加beanA到三级缓存
- 注入收集的属性,注入beanB,触发beanB的创建
- beanB进入doGetBean()方法
- 尝试从缓存里拿beanB,没拿到,一级缓存没拿到,多例不允许去二级三级缓存里找
- 多例集合检查,这时候里面只有beanA,检查通过
- 创建beanB,创建前检查,检查把beanB放到正在创建的多例set集合里,添加成功,此时set集合里有beanA和beanB
- 调用beanB构造方法实例化
- 收集beanB的属性(这里收集到了beanA)
- 多例,不添加beanB到三级缓存
- 注入收集的属性,注入beanA,触发beanA的创建
- beanA进入doGetBean()方法
- 尝试从缓存里拿beanA,没拿到,一级缓存没拿到,多例不允许去二级三级缓存里找
- 多例集合检查,检查失败,发现已经存在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的整个创建流程。虽然我写的很简陋,但是足够帮助你去自己走一遍源码。
附:
- doGetBean()在AbstractBeanFactory类里。
- 去缓存里拿是类DefaultSingletonBeanRegistry的getSingleton()方法。
- spring 2.6以后,默认关闭了循环依赖,如果开启的话,需要在配置文件里进行配置。
- 如果你坚持要使用构造方式的循环依赖,可以加上@Lazy注解,让bean懒加载。