开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天
Spring Bean生命周期,从入门到进阶:
【Spring Bean生命周期】小白也能看懂的入门篇
【Spring Bean生命周期】高手如何解读源码(一) - 资源的扫描和注册
【Spring Bean生命周期】高手如何解读源码(二) - Bean的生命周期
【Spring Bean生命周期】聊透扩展点的流程及应用
1. 什么是循环依赖
代码示例
2. 在Spring中循环依赖会带来什么问题
本身没问题,但在Spring中会出现问题。看过我们前面文章的小伙伴应该知道,Spring的生命周期是这样滴
当存在循环依赖时,代码的调用顺序是这样滴
红色线表示运行流程,代码调用成环了。。等栈溢出吧
3. Spring如何解决循环依赖
名称 | 含义 | |
---|---|---|
singletonObjects | 一级缓存 | 存的是成品对象,实例化和初始化都完成了,我们的应用中使用的对象就是一级缓存中的 |
earlySingletonObjects | 二级缓存 | 存的是半成品,还未完成生命周期,早期占位,用来解决对象创建过程中的循环依赖问题。 |
singletonFactories | 三级缓存 | 存的是lambda表达式,用于生产代理对象。 |
解决思路:提前曝光+引用机制,将实例对象提前放到缓存,等到使用的时候先去缓存找,避免重复被实例化。
3.1 如果只有一级缓存
流程:
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 如果只有二级缓存
流程:
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)。
流程:
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;
}