当前位置: 首页 > news >正文

Spring 为何需要三级缓存解决循环依赖,而不是二级缓存

spring使用三级缓存  ,主要 是  解决循环依赖的问题。

三级缓存 具体 有  哪个三级呢 ?

  • 一级缓存 :

singletonObjects    存储  成品。 也就是  完全初始化后的单例Bean

  • 二级缓存:

earlySingletonObjects  存储  半成品。也就是说,普通对象  已实例化 , 但未完成初始化,是一个 普通对象的半成品Bean

  • 三级缓存 :

singletonFactories  存储 对象模具(对象的工厂), 还不是 一个 半成品 ,还是一个模具级别的制作说明。 或者说,  singletonFactory   还是一个 "半成品的制作说明书" , 还没有开始生产。

注意,这个主要用于  AOP 代理对象的场景,或者说   singletonFactory     更多是 用于生产 AOP对象.

如下表

缓存级别名称(源码字段)存储内容核心作用生命周期阶段

一级缓存

singletonObjects(存储  成品)

完全初始化后的单例Bean

提供最终可用的完整Bean实例

Bean初始化完成后存入,容器销毁时清除

二级缓存

earlySingletonObjects(存储  成品)

已实例化但未完成初始化的半成品Bean

解决普通循环依赖(无AOP代理场景)

从三级缓存的ObjectFactory获取对象后存入,初始化完成后清除

三级缓存

singletonFactories(存储 对象模具)

ObjectFactory(生成Bean的工厂对象)

1. 解决代理对象的循环依赖 
2. 支持BeanPostProcessor的扩展

Bean实例化后存入工厂,获取对象后升级到二级缓存

二级缓存、三级缓存,都是解决循环依赖。

二级缓存 解决  普通对象的循环依赖(无AOP代理场景)循环依赖。

三级缓存 解决   AOP 代理对象(有AOP代理场景)的循环依赖  。

所以,  Spring 解决循环依赖有两个  提前  暴露  :

  • 第二级缓存  提前  暴露 普通对象的半成品:刚刚创建好的对象还没有进行任何赋值的时候,将之暴露出来放到缓存中,供其他 Bean 提前引用(二级缓存)。

  • 第三级缓存 提前  暴露AOP对象 的 对象的工厂(模具):  把 本应该 发生在属性填充后的创建AOP代理操作,提前到属性填充阶段 创建。

为啥要二级不够,要 三级缓存?

在常规情况下,Spring 属性填充阶段(依赖注入阶段),遇到循环依赖,会先通过构造方法把对象 “搭个半成品” 放在二级缓存里,后续再往对象里填充属性值等操作。

但如果涉及 AOP场景,比如 需要  对象 A 配置一个 AOP事务管理器增强 对象 的场景。  假设  按照普通对象的方式处理 循环依赖,  先 在 二级缓存里 放一个  “ 普通对象半成品 “  ,  走完   属性填充阶段(第3阶段,依赖注入阶段),那么 在后面的  初始化 阶段(第4阶段) 生成对应的 AOP 代理对象。  同一个  bean,这样就弄出了 两个对象,这就冲突了。

所以,涉及 AOP场景, 在 属性填充阶段(第3阶段,依赖注入阶段)处理循环依赖的时候  ,用  第三级缓存 的  singletonFactoriy工厂对象(模具对象),提前创建好 proxy代理对象,放入到二级缓存里边的 作为  “  半成品对象 “ 。相当于 本来是 第4阶段干的活儿,提前到第三阶段。

注意: 普通bean的  “  半成品对象 “   和aop 代理bean的  singletonFactoriy工厂对象(模具对象),都是在  Bean 实例化阶段 (第二阶段) 提前放入 的二级缓存   和 第三级缓存    。在  到  属性填充阶段(第3阶段  依赖注入阶段) ,会拿出来使用。

按理说 aop代理bean 是要在   初始化 阶段(第4阶段)    创建的,这样一下就提前到了    属性填充阶段(第3阶段,依赖注入阶段) ,相当于提前暴露。

循环依赖示例分析(后面有细致介绍)

循环依赖处理示例分析(AService ↔ BService):

1、AService实例化,其ObjectFactory被存入三级缓存

2、 AService开始属性填充,发现需要BService

3、 触发BService加载流程:

3.1、 BService实例化,其ObjectFactory存入三级缓存

3.2、 BService开始属性填充,发现需要AService

4、 此时BService可以从三级缓存中获取AService的半成品ObjectFactory,  生成  proxy 对象,存入 二级缓存

5、 完成循环依赖的解决

要深入理解Spring如何解决循环依赖问题,首先需要掌握Bean在Spring容器中的完整生命周期过程

Spring 中生成 Bean 主要包括以下几个阶段:

1、 Bean 定义加载阶段

Spring 容器首先会加载 Bean 的定义信息。这些定义信息来自配置文件(如 XML 配置)或者注解配置(如使用 @Component 系列注解)。

例如,当使用 @ComponentScan 注解时,Spring 会扫描指定包及其子包下的类,查找带有 @Component@Service@Repository@Controller 等注解的类,将这些类的相关信息(如类名、属性等)加载到容器中,形成 BeanDefinition 对象。

2、 Bean 实例化阶段

当需要创建 Bean 实例时,Spring 容器会根据 BeanDefinition 中的信息,通过反射的方式,调用类的构造方法(如 Constructor.newInstance())或工厂方法创建 Bean 的原始对象,此时对象属性均为默认值(如 null 或 0

例如,对于一个简单的带有 @Service 注解的类 MyService,Spring 会使用其无参构造方法创建一个 MyService 对象。

3、属性填充阶段(依赖注入阶段)

创建好 Bean 实例后,Spring 容器会根据 BeanDefinition 中的属性信息,为 Bean 的属性进行赋值。

属性赋值‌:通过 Setter 方法或字段直接赋值(如 @Value("${config.value}")

依赖注入‌:通过 @Autowired@Resource 等注入其他 Bean(如 UserDAO 注入到 UserService

这是一个 的关键阶段。

例如,如果 MyService 类中有一个类型为 MyDao 的属性,并且使用 @Autowired 注解标注,Spring 会在属性填充阶段找到容器中对应的 MyDao Bean,将其注入到 MyService 对象中。

在这个阶段,Spring 会处理各种依赖关系,包括构造方法注入、字段注入和 setter 方法注入等多种方式。

循环依赖在此阶段解决‌:若遇到循环引用(如 A 依赖 B,B 又依赖 A),Spring 通过‌二级缓存、三级缓存‌提前暴露半成品 Bean 完成注入

4、初始化 阶段

在属性填充完成之后,Spring 容器  会进行  Bean 的初始化 。

初始化方法可以通过在 Bean 类中使用 @PostConstruct 注解的方法,或者在 XML 配置中通过 init-method 属性指定的方法。

在 Spring 中,Bean 属性填充完成后的定制化逻辑执行顺序是这样的:

初始化 阶段的操作之1: Aware 接口回调

包括 BeanNameAware(设置 Bean 名称)、BeanFactoryAware(注入 BeanFactory)等接口的实现。

当 Bean 实现这些接口时,Spring 会在属性填充完成后,调用相应的回调方法。

例如,BeanNameAware 的 setBeanName() 方法会在 Bean 的属性填充后被调用,Spring 会将该 Bean 的名称作为参数传递给这个方法。

这样,Bean 就可以获取到自己在 Spring 容器中的名称。

初始化 阶段的操作之2:BeanPostProcessor 前置处理

postProcessBeforeInitialization:这是 BeanPostProcessor 接口中的一个方法。

这个方法会在 Bean 的初始化方法(包括 @PostConstruct 注解方法、InitializingBean 接口的 afterPropertiesSet() 方法、init-method 指定的方法)调用之前执行。

例如,AOP 代理的生成通常在这个阶段完成。Spring 会遍历所有注册的 BeanPostProcessor,依次调用它们的 postProcessBeforeInitialization 方法,对 Bean 进行一些额外的处理,比如为需要进行 AOP 切面操作的 Bean 创建代理对象。

初始化 阶段的操作之3:Bean 初始化方法

  • @PostConstruct 注解方法:如果 Bean 中有使用 @PostConstruct 注解的方法,Spring 会在 postProcessBeforeInitialization 方法执行完成后,调用这个注解的方法。这个方法主要用于执行 Bean 创建完成后的初始化工作,比如资源的加载、对象的初始化等。

  • InitializingBean 接口的 afterPropertiesSet() 方法:如果 Bean 实现了 InitializingBean 接口,Spring 会在 @PostConstruct 注解方法执行之后,调用 afterPropertiesSet() 方法。这个方法也是一个初始化方法,用于进行一些初始化操作。它和 @PostConstruct 注解方法的功能类似,但 @PostConstruct 是 Java 自带的注解,而 InitializingBean 是 Spring 提供的接口。

  • XML 或 @Bean 中指定的 init-method:如果通过 XML 配置或者 @Bean 注解的 initMethod 属性指定了初始化方法,这个方法会在 InitializingBean 接口的 afterPropertiesSet() 方法执行之后被调用。这个方法也是用于执行初始化操作,它提供了另一种配置初始化方法的方式。

@Service
public class MyService {@PostConstructpublic void init() {System.out.println("MyService 初始化方法被调用");}
}

 

这个初始化方法可以用于执行一些 Bean 创建完成后的初始化操作,如资源的加载等。

初始化 阶段的操作之4:BeanPostProcessor 后置处理

postProcessAfterInitialization:这是 BeanPostProcessor 接口中的另一个方法。

它会在 Bean 的初始化方法执行完成后被调用。

Spring 会遍历所有注册的 BeanPostProcessor,依次调用它们的 postProcessAfterInitialization 方法。这个方法可以用于对已经完成初始化的 Bean 进行进一步的处理或者检查,比如对某些特殊场景下的 Bean 进行验证或者修改。

这些后BeanPostProcessor 处理器可以用于实现 AOP(面向切面编程)等功能。

例如,在 AOP 中,AspectJAutoProxyCreator 这样的BeanPostProcessor 后处理器,会在合适的阶段为 Bean 创建 AOP 代理对象。

初始化过程的优先级顺序:

  • Aware 接口优先于所有初始化逻辑‌:确保 Bean 先获取容器基础设施(如名称、工厂)

  • @PostConstruct > afterPropertiesSet() > init-method‌:注解优先于接口,接口优先于配置

这个顺序确保了 Bean 在创建过程中,从属性填充到初始化再到后续处理的各个阶段,都能按照合理的顺序执行相关的定制化逻辑。

5、 Bean 完全可用阶段

当上述所有阶段完成后,Bean 就完全创建好了,可以被应用程序中的其他组件所使用。

例如,当一个控制器类需要使用 MyService Bean 来处理业务逻辑时,就可以从 Spring 容器中获取并使用它。

Spring Bean生命周期解析

spring通过BeanFactory工厂模式管理Bean的生命周期,其中涉及多个关键接口和方法调用。

Bean  实例化阶段

入口方法:AbstractAutowireCapableBeanFactory.doCreateBean()

关键操作:createBeanInstance()通过反射机制创建Bean实例

状态特征:此时对象已完成内存分配,但属性尚未注入(@Autowired等注解未生效)

Aware接口处理

调用顺序: - BeanNameAware.setBeanName() - BeanClassLoaderAware.setBeanClassLoader() - BeanFactoryAware.setBeanFactory()

特殊实现:ApplicationContextAwareProcessor会处理更多Aware接口

BeanPostProcessor处理

前置处理(下图:步骤2):- 遍历所有实现BeanPostProcessor的类- 依次执行postProcessBeforeInitialization()- 注意:正在创建的Bean自身的Processor不会被执行

后置处理(下图:步骤4):

  • 同理执行postProcessAfterInitialization()

前置处理:

自定义初始化

执行InitializingBean.afterPropertiesSet()

执行通过@Bean(initMethod)或XML配置的初始化方法

什么是循环依赖

循环依赖是指spring中两个对象相互依赖,AService 和 BService 互相依赖,代码如下:

@Service
public class AService {@AutowiredBService bService;
}
@Service
public class BService {@AutowiredAService aService;
}

循环依赖解决方案设计

如果让我们设计解决循环依赖的解决方案,很自认也会想到缓存机制,核心流程如下:

1 提前暴露半成品对象‌:创建AService时先通过反射实例化(未初始化),存入缓存池(二级缓存earlySingletonObjects)

2 依赖注入处理‌:当AService需要注入BService时触发BService创建;若BService又依赖AService,则从缓存池中获取AService引用

3  引用传递特性‌:Java的引用传递机制,会确保BService持有的AService引用会随AService后续初始化自动更新为完整对象

4  最终完成‌:BService创建完成后回填至AService,两者均完成生命周期

该方案利用缓存暂存半成品对象(也就是提前暴露半成品到缓存池),通过对象引用动态更新特性打破循环依赖闭环。

如下图从左到右执行

缓存级别名称(源码字段)存储内容核心作用生命周期阶段

一级缓存

singletonObjects

完全初始化后的单例Bean

提供最终可用的完整Bean实例

Bean初始化完成后存入,容器销毁时清除

二级缓存

earlySingletonObjects

已实例化但未完成初始化的半成品Bean

解决普通循环依赖(无AOP代理场景)

从三级缓存的ObjectFactory获取对象后存入,初始化完成后清除

三级缓存

singletonFactories

ObjectFactory(生成Bean的工厂对象)

1. 解决代理对象的循环依赖 
2. 支持BeanPostProcessor的扩展

Bean实例化后存入工厂,获取对象后升级到二级缓存

按照上面思路2个缓存即可,一级缓存存放完整的Bean,二级缓存存放半成品,为什么还要三级缓存存放半成品工厂?

关键在于AOP,AOP 没法使用 二级缓存存放半成品。

正常情况 Bean 属性填充完毕之后,接下来就是执行各种 BeanPostProcessor,如果这个 Bean 中有需要产生代理(比如AOP)的方法,那么系统就会自动配置对应的后置处理器。

假设AOP使用二级缓存,原本的 缓存池中的对象 和新生成的代理是两个不同的对象,占两块不同的内存地址!!!

这就有问题了。

所以,为了解决这个问题,AOP  需要通过三级缓存 对象工厂、对象模具 singletonFactories。

也就是在BService注入AService属性时,判断二级缓存不存在对象,通过三级缓存存放的singletonFactory,产生AService的代理,放到二级缓存

本质上,singletonFactories 是把 AOP 的产生代理过程提前了。

总的来说,Spring 解决循环依赖把握住两个关键点:

  • 提前暴露 普通对象 (二级缓存):刚刚创建好的对象还没有进行任何赋值的时候,将之暴露出来放到缓存中,供其他 Bean 提前引用(二级缓存)。

  • 提前 AOP(三级缓存):本来发生在属性填充后的AOP代理对象创建,提前到属性填充阶段。

循环依赖源码分析

在Spring框架的Bean生命周期中,当进行属性填充阶段时,如果发现某个依赖属性尚未在Spring容器中生成,系统会先触发该属性对象的实例化过程。

关于属性填充的关键机制:

1:Spring会通过ObjectFactory将已实例化但未完成属性填充的Bean提前暴露出来,这种状态被称为"半成品"Bean

2:这种半成品状态意味着Bean已完成实例化,但尚未完成属性注入,是一个不完整的对象实例

Spring通过三级缓存机制解决循环依赖问题:

  • singletonObjects(一级缓存):存储完全初始化的Bean

  • earlySingletonObjects(二级缓存):存储提前暴露的Bean

  • singletonFactories(三级缓存):存储Bean工厂对象

在重点关注下三级缓存:

(1) 实例化的Bean会通过ObjectFactory包装后存入三级缓存(2) singletonFactory是一个匿名内部类,其getObject()方法最终会调用getEarlyBeanReference方法

addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

循环依赖示例分析

循环依赖处理示例分析(AService ↔ BService):

1、AService实例化,其ObjectFactory被存入三级缓存

2、 AService开始属性填充,发现需要BService

3、 触发BService加载流程:

3.1、 BService实例化,其ObjectFactory存入三级缓存

3.2、 BService开始属性填充,发现需要AService

4、 此时BService可以从三级缓存中获取AService的半成品ObjectFactory,  生成  proxy 对象,存入 二级缓存

5、 完成循环依赖的解决

为什么要三级缓存,而不是二级缓存

一级缓存存放完整的Bean,二级缓存存放半成品,为什么还要三级缓存存放半成品工厂?

关键在于AOP,AOP 没法使用 二级缓存存放半成品。

正常情况 Bean 属性填充完毕之后,接下来就是执行各种 BeanPostProcessor,如果这个 Bean 中有需要产生代理(比如AOP)的方法,那么系统就会自动配置对应的后置处理器。

假设AOP使用二级缓存,原本的 缓存池中的对象 和新生成的代理是两个不同的对象,占两块不同的内存地址!!!

这就有问题了。

所以,为了解决这个问题,AOP  需要通过三级缓存 对象工厂、对象模具 singletonFactories。

也就是在BService注入AService属性时,判断二级缓存不存在对象,通过三级缓存存放的singletonFactory,产生AService的代理,放到二级缓存

为什么要三级缓存不是二级的原因 详解

为什么是三级而不是二级缓存跟AOP有关,也就是AOP代理的创建时机与循环依赖的矛盾。

Spring的AOP动态代理是在Bean初始化阶段通过postProcessAfterInitialization后置处理器回调完成的。

然而,当存在循环依赖 且  被AOP拦截的Bean时(如AService依赖BService,BService又依赖AService),需在初始化前就完成代理对象的创建,否则会导致依赖注入失败。

此时,Spring通过getEarlyBeanReference()方法提前生成代理对象,并借助三级缓存机制实现依赖注入。

三级缓存 本质就是将AOP代理创建时机提前,Spring通过三级缓存机制与getEarlyBeanReference()方法的协同,成功解决了带AOP的循环依赖问题

这一机制的核心在于:

  • 提前暴露半成品Bean工厂(通过三级缓存);

  • 按需生成代理对象(通过getEarlyBeanReference());

  • 复用代理对象(通过earlyProxyReferences缓存)。

当AOP遇见循环依赖 场景分析

场景:以AService与BService循环依赖为例

  • AService被AOP切面拦截(通过@Around("execution(* com.example. service.AService.helloA(..))")定义切点)

  • BService未被AOP拦截

存在循环依赖:AService依赖BService,BService依赖AService

AService实例化与半成品工厂暴露

  • 实例化:AService通过构造函数完成实例化(未进行属性填充与初始化)。

  • 半成品暴露:Spring将AService的原始对象封装为ObjectFactory,通过getEarlyBeanReference()方法提前暴露到三级缓存singletonFactories)中。

BService依赖注入AService时的代理处理

BService加载:Spring开始加载BService,实例化后同样通过ObjectFactory暴露到三级缓存。

AService依赖注入:BService在填充属性aService时,发现AService尚未完全初始化,需从缓存中获取。

代理对象生成

  • Spring调用三级缓存中AService的ObjectFactory.getObject()方法,触发getEarlyBeanReference()逻辑。

  • 判断条件:检查后置处理器是否实现SmartInstantiationAwareBeanPostProcessor接口(如AnnotationAwareAspectJAutoProxyCreator)。

  • 代理生成:通过wrapIfNecessary()方法:

  • 检查AService的切点表达式是否匹配(如helloA()方法被拦截)。

  • 使用JDK动态代理或CGLIB生成AService的代理对象aServiceProxy

  • aServiceProxy存入二级缓存earlySingletonObjects),并从三级缓存中移除原ObjectFactory

注入代理对象:BService的aService属性注入的是代理后的aServiceProxy,而非原始AService实例。

注:

@EnableAspectJAutoProxy注解导入的AOP核心业务处理AnnotationAwareAspectJAutoProxyCreator类,它继承了AbstractAutoProxyCreator了,在AbstractAutoProxyCreator类中实现了getEarlyBeanReference()方法。

关键代码如下:

//真正实现了该方法的类就是AbstractAutoProxyCreator
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupportimplements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware { @Overridepublic Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {// 先获取beanName,主要是为FactoryBean类型添加&前缀Object cacheKey = getCacheKey(bean.getClass(), beanName);// 判断是否已经在earlyProxyReferences集合中,不在则添加进去if (!this.earlyProxyReferences.contains(cacheKey)) {this.earlyProxyReferences.add(cacheKey);}// 创建代理对象,如果必要的话return wrapIfNecessary(bean, beanName, cacheKey);}  /** Wrap the given bean if necessary, i.e. if it is eligible for being proxied.* @param bean the raw bean instance* @param beanName the name of the bean* @param cacheKey the cache key for metadata access* @return a proxy wrapping the bean, or the raw bean instance as-is
/protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {// 前面先做一些基本的判断if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;}if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}// Advice/Pointcut/Advisor/AopInfrastructureBean接口的beanClass不进行代理以及对beanName为aop内的切面名也不进行代理// 此处可查看子类复写的shouldSkip()方法if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// Create proxy if we have advice.// 查找对代理类相关的advisor对象集合,此处就与point-cut表达式有关了Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);// 对相应的advisor不为空才采取代理if (specificInterceptors != DO_NOT_PROXY) {this.advisedBeans.put(cacheKey, Boolean.TRUE);// 通过jdk动态代理或者cglib动态代理,产生代理对象,这里传入的是SingletonTargetSource对象喔,对原始bean对象进行了包装Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));// 放入代理类型缓存this.proxyTypes.put(cacheKey, proxy.getClass());return proxy;}// 放入通知缓存this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = this.getCacheKey(bean.getClass(), beanName);if (this.earlyProxyReferences.remove(cacheKey) != bean) {return this.wrapIfNecessary(bean, beanName, cacheKey);}}return bean;}    
}

BService完成初始化

  • 属性填充完成:BService成功注入aServiceProxy后,继续后续的初始化流程(如调用postProcessAfterInitialization)。

  • 最终Bean生成:BService完成初始化后,作为完整Bean存入一级缓存singletonObjects)。

AService的后续初始化与代理对象复用

BService注入完成:AService在属性填充阶段成功注入BService实例后,继续执行初始化流程。

AOP代理复用

  • postProcessAfterInitialization回调中,Spring尝试再次为AService生成代理。

  • 通过检查earlyProxyReferences缓存(保存已生成的代理对象),发现AService已存在代理对象aServiceProxy

  • 避免重复代理:直接返回已存在的代理对象,跳过代理生成逻辑。

最终Bean生成:AService初始化完成后,代理对象aServiceProxy作为最终Bean存入一级缓存。

总结:

缓存层级作用关键操作

三级缓存 (singletonFactories)

存放ObjectFactory工厂对象,用于按需生成半成品Bean

getEarlyBeanReference()触发代理生成

二级缓存 (earlySingletonObjects)

存放已生成的代理对象或半成品Bean

存储aServiceProxy供依赖注入

一级缓存 (singletonObjects)

存放完全初始化的Bean

最终提供完整的Bean实例

(1)三级缓存的必要性

若未使用AOP,Spring仅需通过二级缓存即可解决循环依赖(直接暴露原始对象)。

若使用AOP,必须通过三级缓存:

  • 三级缓存   存放生成早期引用的 ObjectFactory 工厂对象,  在涉及到 AOP 的情况时,Spring 会将一个 SingletonFactory 工厂对象放入三级缓存中。这个工厂对象的作用是延迟创建代理对象。当需要获取 Bean 时,Spring 会先从三级缓存中获取这个工厂对象,通过工厂对象来创建代理对象。

  • 二级缓存存储代理对象,避免重复代理; 主要是用于存储半成品对象,以解决循环依赖问题。它在属性填充阶段之前,就存储未完全初始化的 Bean 实例。

  • 一级缓存提供最终代理后的Bean。一级缓存  是存储最终可用的 Bean 的地方。在完成属性填充和初始化阶段后,Spring 会将最终的 Bean(可能是原始 Bean 或者代理 Bean)放入一级缓存中,供后续使用。

(2)循环依赖的解决逻辑

  • 通过ObjectFactory.getObject()按需生成代理对象;

  • 利用earlyProxyReferences缓存避免重复代理;

  • 代理对象的提前曝光使依赖注入可正常进行。

(3)流程差异对比

  • 无AOP场景:三级缓存退化为二级缓存(半成品直接存入二级缓存)

  • 有AOP场景:三级缓存协同工作,代理对象生成与复用是核心差异点。

最后,也来一张图总结下有AOP的循环依赖如何解决?

重点关注红色部分,有无AOP的区别

所以如果没有AOP的话确实可以两级缓存就可以解决循环依赖的问题,如果加上AOP,两级缓存是无法解决的,不可能每次执行  singleFactory.getObject()方法都 产生一个新的代理对象,所以还要借助另外一个缓存来保存产生的代理对象

循环依赖 失效 的特殊情况

根据前面介绍的思路,以下一些循环依赖场景无法解决。

Spring 的三级缓存机制可以解决大部分循环依赖问题,但在以下三种特殊场景中会失效:

**(1) 构造器注入的循环依赖  **

**(2) 原型作用域(Prototype)的循环依赖  **

**(3) @Async 注解导致的循环依赖  **

基于构造器注入  场景的  循环依赖

如果依赖的对象是基于构造器注入的,那么执行的时候就会报错,代码如下:

@Service
public class AService {BService bService;public AService(BService bService) {this.bService = bService;}
}
@Service
public class BService {AService aService;public BService(AService aService) {this.aService = aService;}
}

我们说先把 AService 原始对象创建出来,存入到缓存池中,然后再处理 AService 中需要注入的外部 Bean 等等,但是,如果 AService 依赖的 BService 是通过构造器注入的,那就会导致在创建 AService 原始对象的时候就需要用到 BService,去创建 BService 时候又需要 AService,这样就陷入到死循环了,对于这样的循环依赖执行时候就会出错。

更进一步,如果我们在 AService 中是通过 @Autowired 来注入 BService 的,那么应该是可以运行的,代码如下:

@Service
public class AService {@AutowiredBService bService;
}
@Service
public class BService {AService aService;public BService(AService aService) {this.aService = aService;}
}

上面这段代码,AService 的原始对象就可以顺利创建出来放到缓存池中,BService 创建所需的 AService 也就能从缓存中获取到,所以就可以执行了。

prototype 对象  场景的  循环依赖

循环依赖双方 scope 都是 prototype 的话,也会循环依赖失败,代码如下:

@Service
@Scope("prototype")
public class AService {@AutowiredBService bService;
}
@Service
@Scope("prototype")
public class BService {@AutowiredAService aService;
}

@Async注解 场景的  循环依赖

带有 @Async 注解的 Bean 产生循环依赖,代码如下

  • AService 使用 @Async 注解定义异步方法

  • BService 依赖 AService

  • AService 依赖 BService(形成循环依赖)

@Service
public class AService {@AutowiredBService bService;@Asyncpublic void hello() {}
}
@Service
public class BService {@AutowiredAService aService;
}

根本原因
- BService 注入的是 AService 的原始对象,而非最终生成的代理对象
- AService 在后续初始化阶段通过 postProcessAfterInitialization 被代理,导致 BService 中的 AService 实例与最终生成的代理对象不一致

@Async 后置处理器的差异

后置处理器类型是否重写getEarlyBeanReference()处理逻辑

AbstractAutoProxyCreator(普通 AOP)

重写

在 getEarlyBeanReference() 中提前生成代理对象

AsyncAnnotationBeanPostProcessor(@Async)

未重写

getEarlyBeanReference() 默认返回原始对象,未生成代理

关键区别:普通 AOP 通过 getEarlyBeanReference() 提前代理,而 @Async 的代理延迟到 postProcessAfterInitialization 执行,导致三级缓存无法提前暴露代理对象。

执行流程分析

步骤 1:AService 实例化与缓存

AService 实例化完成后,封装为 ObjectFactory 存入 三级缓存singletonFactories)。

步骤 2:BService 属性填充

BService 在 populateBean 阶段需要注入 AService  ,  从三级缓存中获取 AService 的 ObjectFactory,调用 getEarlyBeanReference()

问题点:

AsyncAnnotationBeanPostProcessor.getEarlyBeanReference() 未生成代理,返回原始 AService 实例

原始 AService 被注入到 BService 的属性中

步骤 3:BService 初始化完成

BService 完成初始化后存入一级缓存(singletonObjects

步骤 4:AService 初始化

AService 继续执行 initializeBean

在 postProcessAfterInitialization 中,AsyncAnnotationBeanPostProcessor 为 AService 生成代理对象(AServiceProxy

冲突点:此时 AService 的最终实例已变为代理对象,但 BService 中仍持有原始 AService 实例

步骤 5:异常抛出

Spring 检测到 AService 的最终实例与原始实例不一致

抛出 IllegalStateException,提示 Bean 引用不一致

还是通过一张图说明@Async为什么循环依赖会有问题

 

总结一句话:

@Async的处理器没有实现getEarlyBeanReference()方法,也就没有AOP代理的提前产生,造成了前后对象的不一致

总结

问题类型是否可被三级缓存解决核心原因

普通 AOP 循环依赖

可解决

getEarlyBeanReference() 提前生成代理

@Async 循环依赖

无法解决

代理生成延迟到 postProcessAfterInitialization,三级缓存未暴露代理对象

关键结论:Spring 的三级缓存机制依赖 getEarlyBeanReference() 提前暴露代理对象,而 @Async 的代理逻辑未遵循此规则,导致循环依赖无法解决。需通过调整依赖注入方式或延迟注入来规避问题。

@Lazy注解 解决方案 失效 问题

Spring 的三级缓存机制可以解决大部分循环依赖问题,但在以下三种特殊场景中会失效:**(1) 构造器注入的循环依赖  **

**(2) 原型作用域(Prototype)的循环依赖  **

**(3) @Async 注解导致的循环依赖  **

核心矛盾

  • 在初始化阶段,依赖 Bean 尚未完全创建,直接引用会导致异常。

  • Spring 的三级缓存无法提前暴露代理对象(如 @Async 的代理)。

前面提到的三种无法自动解决的循环依赖,都可以通过添加 @Lazy 注解来解决。

@Lazy解决循环依赖问题

在构造器上添加  @Lazy  注解

@Service
public class AService {BService bService;@Lazypublic AService(BService bService) {this.bService = bService;}
}
@Service
public class BService {AService aService;@Lazypublic BService(AService aService) {this.aService = aService;}
}

 

效果

  • 构造器注入的依赖被延迟加载,Spring 为其生成 JDK 动态代理对象

  • 避免初始化阶段直接引用未完成的 Bean

@Lazy 注解可以添加在 AService 或者 BService 的构造方法上,也可以都添加上。

添加上之后,我们再去启动项目,就不会报错了。

这样看起来问题解决了

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml");
AService aService = ctx.getBean(AService.class);
BService bService = ctx.getBean(BService.class);
System.out.println("aService.getClass() = " + aService.getClass());
System.out.println("bService.getClass() = " + bService.getClass());
System.out.println("aService.getbService().getClass() = " + aService.getbService().getClass());
System.out.println("bService.getaService().getClass() = " + bService.getaService().getClass());

最终打印结果如下:

容器中获取到的 AService 和 BService 的 Bean 都是正常的未被代理的对象,事实上我们的原始代码确实也没有需要代理的地方。

但是,AService 中的 BService 以及 BService 中的 AService 却都是代理对象,按理说 AService 中的 BService 应该和我们从 Spring 容器中获取到的 BService 一致,BService 中的 AService 也应该和 Spring 容器中获取到的 AService 一致,但实际上,两者却并不相同。

@Lazy为什么能够解决循环依赖,就是因为 AService 和 BService 各自注入的 Bean 都不是原始的 Bean,都是一个代理的 Bean,AService 中注入的 BService 是一个代理对象,同理,BService 中注入的 AService 也是一个代理对象,需要使用时在调用getTarget获取真正的对象。

这就是 @Lazy 这个注解的工作原理,看名字,加了该注解的对象会被延迟加载,实际上被该注解标记的对象,会自动生成一个代理对象。

前面提到的另外两个问题,也可以通过 @Lazy 注解来解决,代码如下:

原型模式

@Service
@Scope("prototype")
public class AService {@Lazy@AutowiredBService bService;
}
@Service
@Scope("prototype")
public class BService {@Lazy@AutowiredAService aService;
}

@Aysnc

@Service
public class AService {@Autowired@LazyBService bService;@Asyncpublic void hello() {bService.hello();}public BService getbService() {return bService;}
}
@Service
public class BService {@AutowiredAService aService;public void hello() {System.out.println("xxx");}public AService getaService() {return aService;}
}

一句话总结:@Lazy 通过为 Bean 创建代理对象,将依赖注入的时机从初始化阶段推迟到首次使用时,从而解除循环依赖的死锁问题。虽然 Spring 的三级缓存机制无法解决某些特殊场景,但 @Lazy 提供了一种灵活的补救方案。

前面提到的三种无法自动解决的循环依赖,都可以通过添加 @Lazy 注解来解决。

如果是构造器注入,如下:

@Service
public class AService {BService bService;@Lazypublic AService(BService bService) {this.bService = bService;}public BService getbService() {return bService;}
}
@Service
public class BService {AService aService;@Lazypublic BService(AService aService) {this.aService = aService;}public AService getaService() {return aService;}
}

@Lazy 注解可以添加在 AService 或者 BService 的构造方法上,也可以都添加上。

添加上之后,我们再去启动项目,就不会报错了。这样看起来问题解决了,但是其实还是差点意思,小伙伴们看一下我的启动代码:

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml");
AService aService = ctx.getBean(AService.class);
BService bService = ctx.getBean(BService.class);
System.out.println("aService.getClass() = " + aService.getClass());
System.out.println("bService.getClass() = " + bService.getClass());
System.out.println("aService.getbService().getClass() = " + aService.getbService().getClass());
System.out.println("bService.getaService().getClass() = " + bService.getaService().getClass());

最终打印结果如下:

我们从容器中获取到的 AService 和 BService 的 Bean 都是正常的未被代理的对象,事实上我们的原始代码确实也没有需要代理的地方。但是,AService 中的 BService 以及 BService 中的 AService 却都是代理对象,按理说 AService 中的 BService 应该和我们从 Spring 容器中获取到的 BService 一致,BService 中的 AService 也应该和 Spring 容器中获取到的 AService 一致,但实际上,两者却并不相同。

不过这样也好懂了,为什么 Spring 能把一个死结给解开,就是因为 AService 和 BService 各自注入的 Bean 都不是原始的 Bean,都是一个代理的 Bean,AService 中注入的 BService 是一个代理对象,同理,BService 中注入的 AService 也是一个代理对象。

这也是为什么我一开始说这个问题 Spring 解决了又没解决。

其实,这就是 @Lazy 这个注解的工作原理,看名字,加了该注解的对象会被延迟加载,实际上被该注解标记的对象,会自动生成一个代理对象。

前面提到的另外两个问题,也可以通过 @Lazy 注解来解决,代码如下:

@Service
@Scope("prototype")
public class AService {@Lazy@AutowiredBService bService;
}
@Service
@Scope("prototype")
public class BService {@Lazy@AutowiredAService aService;
}

这里 @Lazy 只要一个其实就能解决问题,也可以两个都添加。

对于含有 @Async 注解的情况,也可以通过 @Lazy 注解来解决:

@Service
public class AService {@Autowired@LazyBService bService;@Asyncpublic void hello() {bService.hello();}public BService getbService() {return bService;}
}
@Service
public class BService {@AutowiredAService aService;public void hello() {System.out.println("xxx");}public AService getaService() {return aService;}
}

如此,循环依赖可破!

总而言之一句话,@Lazy 注解是通过建立一个中间代理层,来破解循环依赖的。

@Lazy源码解析

属性注入流程回顾

在Spring的Bean创建过程中,属性注入是关键环节,其核心流程如下:
(1) 原始Bean实例化:通过构造器创建原始Bean对象。

 (2) 调用populateBean方法:触发属性填充逻辑。  

3) 后置处理器判断:执行postProcessAfterInstantiation,检查是否需要调用后置处理器。

 (4) 解析依赖元数据: 

- 调用findAutowiringMetadata获取包含@Autowired@Value等注解的属性或方法。
 - 通过buildAutowiringMetadata封装为InjectedElement(如字段注入元素AutowiredFieldElement)。


(5) 执行依赖注入:调用InjectedElement#inject方法,通过反射完成属性赋值。  

在步骤5中,当解析依赖时(如调用resolveFieldValue),会触发@Lazy注解的处理逻辑。

1. 进入@Lazy解析入口

属性注入的核心逻辑集中在DefaultListableBeanFactory#resolveDependency方法中。会判断注入的属性类型是 Optional、ObjectFactory 还是 JSR-330 中的注解,我们这里都不是,所以走最后一个分支。

2. 判断是否需要延迟加载

getLazyResolutionProxyIfNecessary方法由ContextAnnotationAutowireCandidateResolver实现,核心逻辑如下:

public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, String beanName) {// 检查依赖是否标记@Lazy(字段、方法参数、类上的注解)if (isLazy(descriptor)) {// 创建延迟加载代理return buildLazyResolutionProxy(descriptor, beanName);}return null; // 无需延迟加载,返回null
}
protected boolean isLazy(DependencyDescriptor descriptor) {for (Annotation ann : descriptor.getAnnotations()) {Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class);if (lazy != null && lazy.value()) {return true;}}MethodParameter methodParam = descriptor.getMethodParameter();if (methodParam != null) {Method method = methodParam.getMethod();if (method == null || void.class == method.getReturnType()) {Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class);if (lazy != null && lazy.value()) {return true;}}}return false;}
3. 构建延迟加载代理对象

buildLazyResolutionProxy方法通过Spring AOP生成代理对象,核心逻辑如下:

protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) {// 获取BeanFactory实例,并断言其为DefaultListableBeanFactory类型BeanFactory beanFactory = getBeanFactory();Assert.state(beanFactory instanceof DefaultListableBeanFactory,"BeanFactory needs to be a DefaultListableBeanFactory");final DefaultListableBeanFactory dlbf = (DefaultListableBeanFactory) beanFactory;// 创建TargetSource,用于生成代理对象的目标源TargetSource ts = new TargetSource() {// 返回依赖的目标类类型@Overridepublic Class<?> getTargetClass() {return descriptor.getDependencyType();}// 声明目标源不是静态的@Overridepublic boolean isStatic() {return false;}// 获取目标对象,实现延迟加载逻辑@Overridepublic Object getTarget() {// 如果指定了beanName,则创建一个集合用于存储自动装配的bean名称Set<String> autowiredBeanNames = (beanName != null ? new LinkedHashSet<>(1) : null);// 尝试解析依赖,如果失败则返回nullObject target = dlbf.doResolveDependency(descriptor, beanName, autowiredBeanNames, null);// 如果目标对象为null,则根据目标类型返回相应的空集合或抛出异常if (target == null) {Class<?> type = getTargetClass();if (Map.class == type) {return Collections.emptyMap();} else if (List.class == type) {return Collections.emptyList();} else if (Set.class == type || Collection.class == type) {return Collections.emptySet();} else {throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(),"Optional dependency not present for lazy injection point");}}// 如果指定了beanName且找到了自动装配的bean,则注册依赖关系if (autowiredBeanNames != null) {for (String autowiredBeanName : autowiredBeanNames) {if (dlbf.containsBean(autowiredBeanName)) {dlbf.registerDependentBean(autowiredBeanName, beanName);}}}// 返回解析到的目标对象return target;}// 释放目标对象,此处为空实现@Overridepublic void releaseTarget(Object target) {}};// 创建ProxyFactory实例,并设置TargetSourceProxyFactory pf = new ProxyFactory();pf.setTargetSource(ts);// 获取依赖的目标类型,如果是接口则添加到ProxyFactory中Class<?> dependencyType = descriptor.getDependencyType();if (dependencyType.isInterface()) {pf.addInterface(dependencyType);}// 使用BeanFactory的类加载器创建代理对象,并返回return pf.getProxy(dlbf.getBeanClassLoader());
}
总结:@Lazy的实现逻辑

(1) 注解识别:通过isLazy方法检测依赖是否标记@Lazy。  **(2) 代理生成:使用Spring AOP创建动态代理,代理对象持有LazyInitTargetSource,封装真实Bean的解析逻辑。  ****(3) 延迟触发:代理对象的方法调用会触发getTarget(),此时才真正从容器中获取Bean,实现“依赖注入时给代理,使用时创真实对象”的机制。  **

通过这一流程,@Lazy注解巧妙地利用代理模式和延迟初始化,解决了Spring默认机制无法处理的循环依赖场景,体现了Spring在依赖管理上的灵活性与扩展性。

http://www.lryc.cn/news/579322.html

相关文章:

  • 【网络安全】Webshell命令执行失败解决思路
  • 【第十一篇】SpringBoot缓存技术
  • Javaweb - 10.1 Servlet
  • C盘空间的“元凶”——虚拟内存的神秘面纱
  • css ::before学习笔记
  • 专业AI工具导航与人工智能学习平台AIbase.cn 连接现在与AI未来的智能桥梁
  • YOLO基础算法入门之YOLOv8中的C2f(C2-Faster)高效特征提取结构
  • STC8G 8051内核单片机开发 (中断)
  • 算法学习笔记:4.KMP 算法——从原理到实战,涵盖 LeetCode 与考研 408 例题
  • 家政维修小程序源码php方案解析
  • FASTAPI+VUE3平价商贸管理系统
  • 实际开发如何快速定位和解决死锁?
  • thinkphp中间件
  • 协同过滤推荐算法
  • 动态规划-P1216 [IOI 1994] 数字三角形 Number Triangles
  • RAG实战指南 Day 4:LlamaIndex框架实战指南
  • AutoMedPrompt的技术,自动优化提示词
  • 基于 govaluate 的监控系统中,如何设计灵活可扩展的自定义表达式函数体系
  • 【学习线路】机器学习线路概述与内容关键点说明
  • 解决 Spring Boot 对 Elasticsearch 字段没有小驼峰映射的问题
  • STC8G 8051内核单片机开发(GPIO)
  • “Payload document size is larger than maximum of 16793600.“问题解决(MongoDB)
  • C++ 网络编程(14) asio多线程模型IOThreadPool
  • PyTorch 安装使用教程
  • EXCEL小妙招——判断A列和B列是否相等
  • AI时代SEO关键词策略
  • cv610将音频chn0配置为g711a,chn1配置为 aac编码,记录
  • Java 大视界 -- Java 大数据机器学习模型在自然语言处理中的跨语言信息检索与知识融合(331)
  • Docker:容器化技术的基石与实践指南
  • 机器学习在智能能源管理中的应用:需求响应与可再生能源整合