吊打面试官系列:Spring如何使用三级缓存解决循环依赖

关注我的公众号:【编程朝花夕拾】,可获取首发内容。

01 引言

Spring的面试中,绕不开的话题是循环依赖以及解决方案,而Spring的三级缓存机制正是解决单例 Bean 的循环依赖问题。其核心思想是 提前暴露未初始化的 Bean 实例,结合 ObjectFactory 延迟处理代理对象。

今天我们将一探究竟!

02 循环依赖

循环依赖的问题,就是两个实例对象相互引用,导致无法正常实例化。

java 复制代码
@Component
public class WorkService {

    @Autowired
    private UserService userService;
}

@Component
public class UserService {
	
    @Autowired
    private WorkService workService;
}

其中WorkService就引用userService,userService中引用workService,这就是循环依赖。你是你会发现我们经常这样操作,但是没有发现任何问题。

那是因为Spring已经帮我们解决了。通过构造或者Setter方法注入就是复现问题。

03 三级缓存

源码位置:

org.springframework.beans.factory.support.DefaultSingletonBeanRegistry

java 复制代码
// 一级缓存:存放完全初始化好的 Bean(成品)
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// 二级缓存:存放提前暴露的 Bean(半成品,已实例化但未初始化)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

// 三级缓存:存放 ObjectFactory,用于生成提前暴露的 Bean(可能包含代理)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16)

所谓三级缓存只不是就是三个Map集合而已,通过巧妙的设计解决循环依赖的问题。

04 解决循环依赖的流程

以文章的代码为案例,假设先实例化WorkService。

Spring中的核心方法就是refresh()方法,而实例化的方法就在finishBeanFactoryInitialization(beanFactory)方法里。

4.1 创建WorkService

创建Bean的核心代码:

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

java 复制代码
// 实例化 Bean
instanceWrapper = createBeanInstance(beanName, mbd, args);
Object bean = instanceWrapper.getWrappedInstance();

//......

// 将 Bean 包装成 ObjectFactory 放入三级缓存(仅未初始化且允许循环依赖时)
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

从源码中,可以看出addSingletonFactory方法将 ObjectFactory 存入三级缓存singletonFactories中。

4.2 填充属性

Spring将Bean对象的ObjectFactory放在三级缓存后,并不着急调用getObject方法或者对象本身。而是直接填充WorkService的属性。

填充属性的时候,会通过InstantiationAwareBeanPostProcessor采用反射机制处理注入的属性。

其中关键实现为:

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor

主要的流程如下:

发现userService不存在,就会触发userService的创建。

4.3 创建UserService

创建UserService的时候,重复4.1的方法,将UserService的半成品(未填充属性),同样将 ObjectFactory 存入三级缓存。

此时,为UserService注入WorkService,同样会执行4.2 populateBean()方法。这是发现UserService依赖WorkService,就会尝试获取WorkService。

getSingleton()将是经典的从三级缓存中获取Bean对象的数据。

关键的地方来了:

因为前面的操作都是讲对象的放在了三级缓存中,这里肯定可以取到值。主要代码如下:

java 复制代码
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
    singletonObject = singletonFactory.getObject();
    // 获取到代理对象,放到二级缓存中
    this.earlySingletonObjects.put(beanName, singletonObject);
    // 将三级缓存中的对象删除
    this.singletonFactories.remove(beanName);
}

而singletonFactory.getObject()方法执行的如下图所示:

4.4 完成UserService初始化

UserService获取WorkService的早期引用后,继续完成属性填充和初始化,最终放入一级缓存。

4.5 完成WorkService初始化

WorkService 获得已初始化的 UserService 对象,继续完成属性填充和初始化,最终移入一级缓存,并清理二级缓存。

05 为什么需要三级缓存

Spring设计的三级缓存,二级缓存可不可以解决循环依赖的问题呢?普通的对象是可以的。

问题的根源:代理对象与原始对象的冲突

若只有二级缓存(无 ObjectFactory),无法区分何时创建代理对象。代理对象应在原始对象生成后,由 BeanPostProcessor 在初始化阶段创建,但循环依赖要求 在属性注入阶段提前暴露对象,此时原始对象尚未代理。

三级缓存的核心就是延迟处理代理,确保在循环依赖时提前返回正确的代理对象.

06 三级缓存的流程示意图

相关推荐
captain37618 小时前
MyBatis(入门5)其他查询操作
java·数据库·spring boot·mybatis
Novproxy_Z18 小时前
商用代理避坑:如何识别商家拿机房 IP 冒充住宅 IP
java·网络·tcp/ip
夕除19 小时前
Redis 过期策略
redis·笔记·面试
文丑颜不良啊19 小时前
LeetCode1481~1500题目解析
java·leetcode
2601_9628857219 小时前
quotes 字段全解:涨跌幅、振幅、换手率的口径与单位
java·前端·spring
深蓝AI19 小时前
Python 3.13 的 Queue.shutdown:异步消费者终于能正常收工了
后端
前端精髓19 小时前
调用openrouter的免费API模型
java·前端·javascript
武子康20 小时前
MultiNet6 空闲崩溃:`EXCVADDR=0x8` 后又出现 `CORRUPT HEAP`
人工智能·后端·agent
步行cgn20 小时前
Spring MVC 拦截器 Interceptor 详解:从执行流程到实战应用
java·spring·mvc
文丑颜不良啊20 小时前
LeetCode1381~1400题目解析
java·leetcode