ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Spring 动态代理时是如何解决循环依赖的?为什么要使用三级缓存?

2026/9/16 21:21:32 拓冰建站 浏览量
Spring 动态代理时是如何解决循环依赖的?为什么要使用三级缓存?

首先,我将简单介绍一下Spring框架中的动态代理和循环依赖问题。

动态代理与循环依赖

1. 动态代理

在Spring框架中,动态代理是一种常用的技术,用于实现AOP(面向切面编程)。动态代理允许Spring在运行时为目标对象创建一个代理,以此来插入额外的逻辑,例如事务管理、日志记录等。

2. 循环依赖

循环依赖是指两个或多个Bean相互依赖,形成闭环,导致无法顺利完成依赖注入。例如,Bean A依赖Bean B,而Bean B又依赖Bean A。

最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。

这是大佬写的, 7701页的BAT大佬写的刷题笔记,让我offer拿到手软

解决循环依赖:三级缓存

1. 三级缓存

Spring使用三级缓存解决循环依赖的问题:

  • 一级缓存:存放完全初始化完成的Bean(单例池)。
  • 二级缓存:存放原始Bean的早期引用。
  • 三级缓存:存放Bean的ObjectFactory,用于生成Bean的代理对象。

2. 工作机制

当Spring容器创建Bean时,首先将实例化后的原始Bean放入三级缓存。如果在Bean的完全初始化之前需要引用该Bean,Spring会通过三级缓存中的ObjectFactory来创建Bean的代理对象,并将其提升到二级缓存中。这样,即使在Bean还未完全初始化之前,也能通过代理对象来解决循环依赖的问题。

使用场景与性能优化

1. 使用场景

  • 事务管理:使用动态代理管理事务边界。
  • 日志记录:在方法执行前后添加日志记录。
  • 安全性:在方法调用前进行权限检查。

2. 性能优化

  • 减少代理创建:仅对关键服务进行代理,避免过度使用动态代理。
  • 懒加载:适当使用懒加载,延迟Bean的初始化。

代码示例

让我们通过一个简单的例子来理解Spring的动态代理和循环依赖的处理:

// 服务接口
public interface UserService {void addUser(String username);
}// 实现类
public class UserServiceImpl implements UserService {@Autowiredprivate OrderService orderService; // 依赖OrderService@Overridepublic void addUser(String username) {System.out.println("Adding user: " + username);// ... 其他逻辑 ...}
}// 另一个服务
public class OrderService {@Autowiredprivate UserService userService; // 依赖UserServicepublic void createOrder(String username) {System.out.println("Creating order for: " + username);// ... 其他逻辑 ...}
}

在这个例子中,UserService 和 OrderService 互相依赖。Spring通过三级缓存机制,确保这种循环依赖不会导致问题。

Spring中三级缓存机制是如何工作的,特别是在解决循环依赖的情况下。

我会用一个稍微复杂点的例子来展示这一机制。

场景设定

假设我们有两个组件,A 和 B,它们互相依赖。为了简化示例,我们假设它们都是单例(Spring默认的作用域)。

@Component
public class A {@Autowiredprivate B b;public A() {System.out.println("A 创建");}// A的其他方法...
}@Component
public class B {@Autowiredprivate A a;public B() {System.out.println("B 创建");}// B的其他方法...
}

三级缓存的工作原理

实例化

当Spring容器启动时,它会尝试创建这些Bean。

首先,它创建了A的实例。在这个过程中,Spring发现A需要依赖B。

然后,它开始创建B的实例。同样,在创建B时,发现B需要依赖A。

三级缓存介入

此时,A的实例已经被部分创建,并存放在三级缓存中。

当Spring为B创建依赖A时,它不会重新创建A的实例。相反,它会使用存在于三级缓存中的A的早期引用。

这个早期引用足以满足B对A的依赖,从而允许B的创建过程继续。

依赖注入与完成创建

一旦B被成功创建,Spring会完成对A的依赖注入。

这时,A和B都已经被完全创建,并存放在Spring的一级缓存(即单例池)中。

关键点

这个过程中的“早期引用”通常是通过使用ObjectFactory创建的代理对象。

三级缓存主要用于解决这种循环依赖的问题,同时也确保了Spring容器的线程安全。

注意事项

虽然三级缓存机制很强大,但它仅适用于单例作用域的Bean。

对于原型作用域的Bean,Spring不会尝试解决循环依赖,这可能会导致BeanCurrentlyInCreationException异常。

推荐一个学习 Spring源码分析 的专栏文章

  • 01、Spring源码分析 - 01-DispatcherServlet注册过程
  • 02、Spring源码分析 - 02-Resource
  • 03、Spring源码分析 - 03-ResourceLoader
  • 04、Spring源码分析 - 04-类型转换
  • 05、Spring源码分析 - 05-字段格式化
  • 06、Spring源码分析 - 06-ResolvableType
  • 07、Spring源码分析 - 07-BeanWrapper
  • 08、Spring源码分析 - 08-DataBinder
  • 09、Spring源码分析 - 09-PropertySourcesPropertyResolver
  • 10、Spring源码分析 - 10-Environment
  • 11、Spring源码分析 - 11-BeanFactory的实现
  • 12、Spring源码分析 - 12-BeanFactory创建Bean的重要流程图
  • 13、Spring源码分析 - 13-DispatcherServlet中WebApplicationContext启动过程
  • 14、Spring源码分析 - 14-Spring默认重要的组件
  • 15、Spring源码分析 - 15-ConfigurationClassPostProcessor
  • 16、Spring源码分析 - 16-AutowiredAnnotationBeanPostProcessor
  • 17、Spring源码分析 - 17-RequiredAnnotationBeanPostProcessor
  • 18、Spring源码分析 - 18-CommonAnnotationBeanPostProcessor
  • 19、Spring源码分析 - 19-ConfigurationClassPostProcessor
  • 20、Spring源码分析 - 20-Spring事件/监听器机制
  • 21、Spring源码分析 - 21-Spring AOP概述
  • 22、Spring源码分析 - 22-Spring AOP的实现原理之ProxyFactoryBean
  • 23、Spring源码分析 - 23-TargetSource目标源
  • 24、Spring源码分析 - 24-基于注解@Aspect的AOP实现
  • 25、Spring源码分析 - 25-Spring异步实现原理
  • 26、Spring源码分析 - 26-TaskExecutor与TaskScheduler
  • 27、Spring源码分析 - 27-基于注解@Scheduled定时任务实现
  • 28、Spring源码分析 - 28-Spring缓存原理详解
  • 29、Spring源码分析 - 29-JdbcTemplat的设计与实现
  • 30、Spring源码分析 - 30-Spring编程式事物的设计与实现
  • 31、Spring源码分析 - 31-Spring声明式事物的设计与实现
  • 32、Spring源码分析 - 32-基于注解@Transactional的事物实现
  • 33、Spring源码分析 - 32-基于注解@Transactional的事物实现
  • 34、Spring源码分析 - 34-Spring Bean作用域的设计与实现
  • 35、Spring源码分析 - 35-Spring MVC设计原理
  • 36、Spring源码分析 - 36-Spring MVC参数值的绑定
  • 37、Spring源码分析 - 37-Spring MVC的异常处理
  • 38、Spring源码分析 - 38-RestTemplate详解
  • 39、Spring源码分析 - 39-Spring容器生命周期回调接口LifeCycle
  • 40、Spring源码分析 - 40-Spring Validation参数校验的使用与原理
  • 41、Spring源码分析 - 41-ClassPathBeanDefinitionScanner
  • 42、Spring源码分析 - 42-@Conditional详解

真实案例比较

在实际开发中,我们通常会遇到以下几种情况:

  1. 简单的CRUD操作:通常不需要动态代理。
  2. 复杂的业务逻辑:涉及事务、安全性检查时,动态代理非常有用。
  3. 高性能要求的场景:在这种场景下,过度使用动态代理

最后说一句(求关注,求赞,别白嫖我)

最近无意间获得一份阿里大佬写的刷题笔记,一下子打通了我的任督二脉,进大厂原来没那么难。

这是大佬写的, 7701页的BAT大佬写的刷题笔记,让我offer拿到手软

项目文档&视频:

项目文档 & 视频

本文,已收录于,我的技术网站 ddkk.com,有大厂完整面经,工作技术,架构师成长之路,等经验分享

求一键三连:点赞、分享、收藏

点赞对我真的非常重要!在线求赞,加个关注我会非常感激