关注微信公众号 “程序员小胖” 每日技术干货,第一时间送达!
引言
在Spring框架的编程征途中,循环依赖犹如一场突如其来的风暴,让无数开发者措手不及。它既是代码世界的隐形陷阱,也是检验技术水平的试金石。本文,我们将化身探险家,深入Spring的核心地带,揭开循环依赖的神秘面纱。从困惑到清晰,从理论到实践,一步步教你识别、理解直至破解这一难题,确保你的项目稳健前行。
循环依赖
循环依赖(circular dependency)简单说就是A对象依赖了B对象,B对象依赖了A对象,在A类的实例化过程中,Spring发现需要注入B类的bean,于是去创建B类的bean。同理,B类在创建过程中又需要A类的bean,于是形成了一个循环。
class A{
@autowired
private B b;
public void test(){
System.out.println(b.test());
}
}
class B{
@autowired
private A a;
public void test(){
System.out.println(a.test());
}
}
A a = new A();
B b = new B();
a.b = b;
b.a = a;
在Spring中,一个对象并不是简单new出来了,而是会经过一系列的Bean的生命周期,就是因为Bean的生命周期所以才会出现循环依赖问题。当然,在Spring中,出现循环依赖的场景很多,有的场景Spring自动帮我们解决了,而有的场景则需要程序员来解决,要想明白Spring中的循环依赖,得先明白Spring中Bean的生命周期。
Spring Bean的生命周期
如果想深度理解Spring Bean的生命周期,可以看我以往的文章《*******》
这里简单描述下Bean的生命周期
- Spring扫描class得到BeanDefinition
- 根据得到的BeanDefinition去生成bean
- 首先根据class推断构造方法
- 根据推断出来的构造方法,反射,得到一个原始对象对象
- 填充原始对象中的属性(依赖注入)
- 如果原始对象中的某个方法被AOP了,那么则需要根据原始对象生成一个代理对象
- 把最终生成的代理对象放入单例池(源码中叫做singletonObjects)中,下次getBean时就直接从单例池取即可
可以发现,在Spring中,构造一个Bean,包括了new这个步骤(第4步构造方法反射)。得到一个原始对象后,Spring需要给对象中的属性进行依赖注入,那么这个注入过程是怎样的?
A类中存在一个B类的b属性,所以,当A类生成了一个原始对象之后,就会去给b属性去赋值,此时就会根据b属性的类型和属性名去BeanFactory中去获取B类所对应的单例bean。如果此时BeanFactory中存在B对应的Bean,那么直接拿来赋值给b属性;如果此时BeanFactory中不存在B对应的Bean,则需要生成一个B对应的Bean,然后赋值给b属性。
问题来了,生成B对应Bean的过程中 B类中存在A类的a属性,在创建B的bean的过程中就需要A类对应的Bean,但是B类Bean的创建条件是A类Bean在创建过程中的依赖注入,这里就出现了循环依赖。
由上图可见 A和B的Bean一直都创建不出来,这就是Spring中的循环依赖
三级缓存
Spring就是三级缓存机制帮开发者解决了部分循环依赖的问题。接下来我们了解下三级缓存是怎么处理的。
**一级缓存为(singletonObjects):**缓存的是已经经历了完整生命周期的bean对象。
**二级缓存为(earlySingletonObjects):**比singletonObjects多了一个early,表示缓存的是早期的bean对象。早期是什么意思?表示Bean的生命周期还没走完就把这个Bean放入了earlySingletonObjects。
**三级缓存为(singletonFactories):**缓存的是ObjectFactory,表示对象工厂,表示用来创建早期bean对象的工厂。
如何把两个打架的人停止打架呢,必须得有个劝架的人,解决这个问题也是同理。
由上图可见,其实从头到尾只多引入了一个A原始对象 既然一个缓存可以解决的问题为啥还需要引入三级缓存呢?
其实这是难点 仔细想想 A的原始对象注入给B的属性后 A的原始对象进行了AOP产生了一个代理对象,对于A来说它的bean对象是AOP之后的代理对象 B的a属性其实是AOP之前的对象 其实归根到底来说 B依赖的A和最终的A不是同一个对象。
AOP就是通过一个BeanPostProcessor来实现的,这个BeanPostProcessor就是AnnotationAwareAspectJAutoProxyCreator,它的父类是AbstractAutoProxyCreator,而在Spring中AOP利用的要么是JDK动态代理,要么CGLib的动态代理,所以如果给一个类中的某个方法设置了切面,那么这个类最终就需要生成一个代理对象。
一般过程就是:A类—>生成一个普通对象–>属性注入–>基于切面生成一个代理对象–>把代理对
象放入singletonObjects单例池中。
而AOP可以说是Spring中除开IOC的另外一大功能,而循环依赖又是属于IOC范畴的,所以这两大功
能想要并存,Spring需要特殊处理。如何处理的,就是利用了第三级缓存singletonFactories。
首先,singletonFactories中存的是某个beanName对应的ObjectFactory,在bean的生命周期中,
生成完原始对象之后,就会构造一个ObjectFactory存入singletonFactories中。这个ObjectFactory
是一个函数式接口,所以支持Lambda表达式:() -> getEarlyBeanReference(beanName, mbd,bean)
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;
}
该方法会去执行SmartInstantiationAwareBeanPostProcessor中的getEarlyBeanReference方法,
而这个接口下的实现类中只有两个类实现了这个方法,一个是AbstractAutoProxyCreator,一个是
InstantiationAwareBeanPostProcessorAdapter,它的实现如下:
// InstantiationAwareBeanPostProcessorAdapter
@Override
public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
return bean;
}
// AbstractAutoProxyCreator
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey);
}
在整个Spring中,默认就只有AbstractAutoProxyCreator真正意义上实现了getEarlyBeanReference方法,而该类就是用来进行AOP的。上文提到的AnnotationAwareAspectJAutoProxyCreator的父类就是AbstractAutoProxyCreator。
那么getEarlyBeanReference方法到底在干什么? 首先得到一个cachekey,cachekey就是beanName。 然后把beanName和bean(这是原始对象)存入earlyProxyReferences中 调用wrapIfNecessary进行AOP,得到一个代理对象。
那么,什么时候会调用getEarlyBeanReference方法呢?回到循环依赖的场景中
总结
在深入剖析Spring框架处理循环依赖的巧妙机制中,三级缓存策略无疑是其智慧的结晶。这一旅程不仅揭示了循环依赖这一编程挑战的深层原理,还展现了Spring如何利用singletonObjects、earlySingletonObjects与singletonFactories这三个级别的缓存,优雅地解决了单例Bean之间的依赖死锁问题。