版本:
springboot :2.2.x
spring 5.x
java 8(springboot 2.x 都是 java 8)
我们的用例:
// 单例
@Component
public class CircleDependent1 {
@Resource // 默认按名称注入
private CircleDependent2 circleDependent2;
}
// 单例
@Component
public class CircleDependent2 {
@Resource // 默认按名称注入
private CircleDependent1 circleDependent1;
}
这时候,CircleDependent1、CircleDependent2 是形成循环依赖。
首先我们要明确,我们只探究 单例通过属性注入的循环依赖问题,因为一般我们说 Spring IOC 内部三级缓存循环依赖的知识,也只是针对这种场景进行讨论。
我们提到的三级缓存,指的是在 Bean 单例注册器 DefaultSingletonBeanRegistry 里的几个 Map,我们提前熟知,最好背下来
public class DefaultSingletonBeanRegistry ... {
// 一级缓存:单例对象(成品)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:早期单例对象
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
// 三级缓存:单例工厂(解决AOP问题)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}
SpringBoot 的启动链路,直到我们处理示例的问题,链路我贴在这里,具体启动流程可以参考 SpringBoot 的启动流程,这里不讨论
preInstantiateSingletons:897, DefaultListableBeanFactory (org.springframework.beans.factory.support)
finishBeanFactoryInitialization:879, AbstractApplicationContext (org.springframework.context.support)
refresh:551, AbstractApplicationContext (org.springframework.context.support)
refresh:141, ServletWebServerApplicationContext (org.springframework.boot.web.servlet.context)
refresh:931, SpringApplication (org.springframework.boot)
refreshContext:558, SpringApplication (org.springframework.boot)
run:388, SpringApplication (org.springframework.boot)
run:1415, SpringApplication (org.springframework.boot)
run:1399, SpringApplication (org.springframework.boot)
main:28, LaunchScriptTestApplication (org.springframework.boot.launchscript)
DefaultListableBeanFactory#preInstantiateSingletons 方法里,有以下抽象:
@Override
public void preInstantiateSingletons() throws BeansException {
List<String> beanNames = new ArrayList<>(this.beanDefinitionNames); // 扫描注册的 Bean 名称集合
for (String beanName : beanNames) { // 循环所有的 Bean 名称
// ..
getBean(beanName); // 通过名称获取 Bean,从这里打断点能很好地观察用户定义bean的生命周期
}
// ..
}
这段逻辑,就是将 Spring 扫描的 bean 名称集合,用 AbstractBeanFactory#getBean 进行统一的 Bean 获取(这里用 get,语义上为“获取”),其没有任何返回值,说明这个方法就是获取Bean,而且将其添加到容器中,方法是内部收敛的、闭环的。我们可以有两种猜测:
- void getBean(beanName) 会将 beanName 及其整条依赖链路全部创建完毕;
- void getBean(beanName) 只会将 beanName 创建,然后链接直接所需依赖的引用;
我们带着问题往下探究
AbstractBeanFactory#getBean 调用了 AbstractBeanFactory#doGetBean 执行获取操作,这个方法是通用的下层方法,由于可能从缓存中获取,所以不一定“创建”,才叫“获取”。
由于我们探究循环依赖问题,将无关代码移除,后续都只关注循环依赖相关部分。
此方法逻辑简化如下:
protected <T> T doGetBean(
String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// ..
Object sharedInstance = getSingleton(beanName); /// 从三级缓存中获取单例
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); // 处理FactoryBean逻辑
// Create bean instance.
if (mbd.isSingleton()) { // 单例作用域处理
sharedInstance = getSingleton(beanName, () -> { /// 通过回调创建单例(核心入口)
try {
return createBean(beanName, mbd, args); /// 实际创建bean实例(模板方法)
}
catch (BeansException ex) {
destroySingleton(beanName); // 创建失败时清理单例缓存
throw ex;
}
});
// 由于是讲解循环依赖问题,不会走到原型作用域 prototype 的处理逻辑
// ..
bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
return (T) bean; // 返回最终bean实例
}
步骤拆解如下:
doGetBean
├─ getSingleton 检查单例缓存 → 命中则直接返回
├─ 按作用域创建 Bean:
└─ 单例 → getSingleton() (顺便注册 get不到bean就会触发的回调入口 createBean())
├─ getObjectForBeanInstance 处理 FactoryBean 的逻辑,获取 sharedInstance 的 bean
└─ 返回获取到的bean
以上步骤中,获取 Bean,get不到 bean, 就执行回调入口 createBean 去创建 bean,逻辑如下:
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Object singletonObject = this.singletonObjects.get(beanName); // 从第一级缓存(完全初始化的单例)获取
if (singletonObject == null) { // 取不到
beforeSingletonCreation(beanName); // 标记bean正在创建(解决循环依赖的关键入口)
try {
singletonObject = singletonFactory.getObject(); /// 通过 singletonFactory 单例工厂 回调获取对象
newSingleton = true; // 标记为新创建的实例
}
if (newSingleton) { // 如果是新创建的单例
addSingleton(beanName, singletonObject); // 添加到一级缓存,清除二三级缓存
}
}
return singletonObject; // 返回最终的单例实例
}
getSingleton 方法的逻辑简介:看看有没有(singletonObjects 一级缓存中存在),如果有则返回,没有则通过单例工厂创建,然后移动到一级缓存。
我们回到我们的例子,我们先处理的是 1对象(为了直观,之后将 CircleDependent1 简化描述为 1对象) 的逻辑不存在于三级缓存,所以通过 singletonFactory.getObject() 回调到匿名函数(回到前文可看到该匿名函数),调用 createBean 去创建 bean(此时 create ,而不是 get,注意这个差别,意味着这时候才是真正的创建逻辑,毕竟从缓存中没有拿到现有的只能去创建,命名十分合理)。
那么 createBean 的“创建” bean 的过程又是怎么样的呢?
我们的 AbstractBeanFactory 抽象工厂的实现是 AbstractAutowireCapableBeanFactory#createBean
@Override // 实例化
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// ..
/// 正式创建 Bean 实例(包含 实例化 -> 属性注入 -> 初始化完整流程)
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
return beanInstance; // 返回 bean 实例
}
调用了 doCreateBean 真正执行创建逻辑
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// ..
/// 创建 Bean 实例
instanceWrapper = createBeanInstance(beanName, mbd, args); /// 核心 实例化,使用策略模式:构造器注入/工厂方法/简单实例化
// 获取原始 bean 实例
Object bean = instanceWrapper.getWrappedInstance();
/// 提前暴露单例引用(解决循环依赖核心)
// Eagerly cache singletons to be able to resolve circular references
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); /// 5. 将单例工厂添加到三级缓存(singletonFactories)提前暴露对象,解决循环依赖
}
/// Bean 初始化
// Initialize the bean instance.
Object exposedObject = bean;
try {
/// 以下是 spring 循环注入的关键,处理@Autowired/@Value/@Resource等注解,通过BeanPostProcessor进行属性注入,触发依赖bean的实例化(可能形成循环依赖)
populateBean(beanName, mbd, instanceWrapper);
/// 属性注入阶段 三级缓存机制在这里面
exposedObject = initializeBean(beanName, exposedObject, mbd); // 初始化阶段 (AOP代理在此阶段完成) 执行aware接口中的方法,初始化方法,完成AOP代理
}
return exposedObject; // 返回最终处理后的Bean(可能是代理对象)
}
总体流程是 doGetBean 先 createBeanInstance 实例化 Bean,然后提前将 getEarlyBeanReference 这个方法包装成匿名函数(ObjectFactory 实现),addSingletonFactory 加到第三级缓存(可处理 AOP 代理对象注入问题),接下来再调用 populateBean 进行属性注入,再 initializeBean 初始化。
这四个步骤:
- createBeanInstance 实例化 Bean,通过调用链路 createBeanInstance -> instantiateBean -> SimpleInstantiationStrategy#instantiate -> BeanUtils#instantiateClass -> java.lang.reflect.Constructor#newInstance,最终就是构造器反射new出Instance(实例)
- addSingletonFactory ,添加单例工厂,如果是单例、允许循环引用、正在创建中,那么将单例工厂 ObjectFactory 的引用加入三级缓存,提前暴露引用,后续可以通过工厂函数对 getEarlyBeanReference 的调用而拿到引用(听说是屏蔽 AOP 代理细节,具体先不探究)
- populateBean,意思是 注入bean,是这几个步骤的核心,会进入一个很长的注入属性的逻辑链路里,反复迭代递归,方法栈会很“满”。
- initializeBean,意思是 初始化bean,完成 invokeAwareMethods 方法对 Aware 的执行、applyBeanPostProcessorsBeforeInitialization 完成初始化前的Bean后置处理器执行、 invokeInitMethods 初始化方法执行、applyBeanPostProcessorsAfterInitialization 完成初始化后的Bean后置处理器执行。(听说还有完成AOP代理,具体先不探究)
目前为止的流程示意图
getBean() // 获取 Bean(不一定创建)
└─> doGetBean() // 执行 获取 Bean
└─> createBean() // 创建 bean,如果 doGetBean 不在一级缓存中时回调执行
├─> createBeanInstance() // 创建bean实例,构造器反射创建对象
├─> addSingletonFactory() // 添加单例工厂到三级缓存,暴露早期引用
├─> populateBean() // 注入 bean
└─> initializeBean() // 初始化 bean
其他都很好理解,只有 populateBean 我们还不知道如何对 bean 进行 populate(注入)
populateBean 方法的简化逻辑如下:
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
// ..
// 遍历所有的 BeanPostProcessor Bean 后置处理器
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof InstantiationAwareBeanPostProcessor) {
PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);/// 后处理器处理属性值(重要:AutowiredAnnotationBeanPostProcessor/CommonAnnotationBeanPostProcessor负责在此注入依赖)
}
applyPropertyValues(beanName, mbd, bw, pvs);// 属性赋值的最终执行点
}
}
核心步骤:
- getBeanPostProcessors 遍历所有的 BeanPostProcessor,其中,重点关注执行 InstantiationAwareBeanPostProcessor#postProcessProperties 接口,实现依赖注入,如果是 @Resource 会走到 CommonAnnotationBeanPostProcessor#postProcessProperties 实现 ,如果是 @Autowired 会走到 AutowiredAnnotationBeanPostProcessor#postProcessProperties 实现。其实底层都差不多,都是同样的元素注入流程。
- applyPropertyValues 进行属性最终赋值
以上步骤的核心流程,postProcessProperties 流程都差不多,由于我们用例是 @Resource,所以走到 CommonAnnotationBeanPostProcessor#postProcessProperties,持续往下看
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs); /// 注入
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);
}
return pvs;
}
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
// ..
for (InjectedElement element : elementsToIterate) { // 遍历所有注入元素(字段或方法)
element.inject(target, beanName, pvs); /// 执行单个元素的注入操作(多态调用,实际可能是字段或方法注入)
}
}
elementsToIterate 是需要迭代注入的元素,这里 element 只有 2对象,跟我们的用例是一致的
protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs)
throws Throwable {
// ..只展示字段注入逻辑
Field field = (Field) this.member;
ReflectionUtils.makeAccessible(field);
field.set(target, getResourceToInject(target, requestingBeanName));
}
getResourceToInject 字面含义为 获取资源去注入,那我们继续链路往下
CommonAnnotationBeanPostProcessor.ResourceElement#getResourceToInject
└─> CommonAnnotationBeanPostProcessor#getResource
└─> CommonAnnotationBeanPostProcessor#autowireResource
└─> AbstractAutowireCapableBeanFactory#resolveBeanByName
└─> AbstractBeanFactory#getBean
└─> AbstractBeanFactory#doGetBean
2对象的创建,最终走到了 doGetBean 的逻辑,这个逻辑如果仔细看,你会觉得似曾相识。
目前为止的流程示意图
getBean() // 获取 Bean(不一定创建)
└─> doGetBean() // 执行 获取 Bean
└─> createBean() // 创建 bean,如果 doGetBean 不在一级缓存中时回调执行
├─> createBeanInstance() // 创建bean实例,构造器反射创建对象
├─> addSingletonFactory() // 添加单例工厂到三级缓存,暴露早期引用
├─> populateBean() // 注入 bean
└─> getBean () // 依赖的 bean 通过其构建
└─> doGetBean() // 执行 获取 Bean
└─> initializeBean() // 初始化 bean
其实又回到了原来的 AbstractBeanFactory#getBean -> AbstractBeanFactory#doGetBean 的逻辑,也就是说:
通过populateBean注入依赖又回到了我们的 doGetBean 去获取 Bean 的流程,也就是说是递归调用。我们知道,递归调用就会有递归调用的算法思路。Spring IOC 获取 Bean 递归三要素如下:(不是很准确的描述,仅供参考)
- 函数:getBean(beanName)
- 边界:inject 时,elementsToIterate 为空
- 递推公式:getBean(beanName) = doGetBean(beanName) = populateBean(createBeanInstance(beanName))
这时候,我们就可以看出 addSingletonFactory 这个步骤的前瞻性了,假如没有提前暴露 1对象的引用到第三级缓存中,2对象创建时 populateBean 为了注入 1对象, getBean 不到就又得去 createBean 创建1对象,递归就进入了死循环。而提前暴露早期工厂到缓存中,就可以提前返回,注入时就可以直接使用。
其实到这里,已经将循环依赖解决的方法讲完了,但是其实还有很多地方是有疑问的:
- 用两级缓存,一个暴露引用,一个成品仓库,其实就可以解决循环依赖,为什么Spring要用三级缓存?
- 为什么暴露的早期引用是工厂匿名函数,而不是普通的引用呢?
- 三级缓存分别扮演了什么角色?
- 完整的数据链路是什么样的呢?
- …
后续再一一解答,这里先挖个坑
微信公众号关注 「源码启示录」
持续推送全网最易懂、最优质、最原创的源码解析