Spring循环依赖(下)
上文中把springbean
的创建过程大致整理了一下,但是似乎还没有回答上文背景中提出的问题,为什么在某个方法上加了一个@Async
注解之后,导致出现了循环依赖报错,而上文中有两个简单小例子,其中使用了AOP
,却没有出现循环依赖报错?(如果对spring
中的AOP
和@Async
实现方式有一个大概了解的话,应该知道二者都是通过生成代理对象的方式实现。)
这里举两个个例子:
- A对象和B对象相互依赖,且A对象中的
printHelloWorld
标记@Async
。
1 |
|
- A对象和B对象相互依赖,且A对象中的
printHelloWorld
被AOP
。
1 |
|
在第一个例子中,在创建B对象时,B通过第三级缓存中的提前暴露对象工厂获得的A对象是A在创建过程中的空对象。当A的经过初始化之后(在initialization()
方法中会执行实现了BeanPostProcessor
后置处理器中的postProcessAfterInitialization
方法,而@Async
的实现就是AsyncAnnotationBeanPostProcessor
,经过该方法后生成的代理对象为proxyA
),所以就导致A对象提前暴露给B对象注入的对象与最后初始化生成的对象不一致,根据上文说的判定逻辑,最终导致循环依赖报错。
在第二个例子中,在创建B对象时,B通过第三级缓存中的提前暴露对象工厂获取的A对象是经过BeanPostProcessor
后置处理器的一个子扩展接口SmartInstantiationAwareBeanPostProcessor
的getEarlyBeanReference
方法,处理之后变为代理对象proxyA
,(这里通过提前暴露对象的工厂获取到的就是代理对象,也就是提前暴露的对象就是代理对象),所以进入上文的判定逻辑,正常返回。
注意:二者之前的差别就是提前暴露的对象是不是代理对象。获取说是代理对象的暴露时机有所不同。那么为什么二者暴露代理对象的时机有所不同?二者都是通过BeanPostProcessor
的实现进行处理,AOP
实现类和@Async
的实现类AnnotationAwareAspectJAutoProxyCreator
和AsyncAnnotationBeanPostProcessor
有一个很大的区别是前者实现了SmartInstantiationAwareBeanPostProcessor
接口,在提前暴露对象工厂中,只会执行实现了这个接口的getEarlyBeanReference
方法来获取提前暴露对象。
1 | protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) { |
关键区别
1. 代理对象的作用范围
AsyncAnnotationBeanPostProcessor
:- 它为标注了
@Async
的方法创建代理对象,仅拦截这些方法的调用。 - 代理的目标是实现异步执行,与整个 bean 的生命周期管理无关。
- 代理对象的创建发生在 bean 初始化后,仅用于增强方法行为,不影响依赖注入。
- 它为标注了
AbstractAutoProxyCreator
:- 它的代理通常覆盖整个 bean,拦截所有方法调用。
- 它的目的是实现 AOP 功能(如事务管理、权限验证等),可能影响依赖注入。
- 代理对象需要提前暴露,以确保循环依赖场景下,其他 bean 引用的是代理对象。
2. 代理创建时机
AsyncAnnotationBeanPostProcessor
:- 创建代理发生在
postProcessAfterInitialization
阶段。此时,bean 已经完成了依赖注入和初始化。 - 不涉及循环依赖,因为它仅修改方法调用,不影响 bean 的结构或生命周期。
- 创建代理发生在
AbstractAutoProxyCreator
:- 代理创建发生在
postProcessBeforeInstantiation
或getEarlyBeanReference
阶段,以确保在循环依赖场景下,能够提前暴露代理对象。 - 需要通过
SmartInstantiationAwareBeanPostProcessor
来参与三级缓存的管理,确保依赖注入过程中代理对象被正确使用。
- 代理创建发生在
3. 是否需要支持循环依赖
AsyncAnnotationBeanPostProcessor
:- 不需要支持循环依赖。异步方法的功能是在运行时拦截,而不是在依赖注入阶段。
- 因此,它无需实现
SmartInstantiationAwareBeanPostProcessor
或getEarlyBeanReference
。
AbstractAutoProxyCreator
:- 必须支持循环依赖,确保代理对象在注入过程中被正确引用。
- 如果不实现
SmartInstantiationAwareBeanPostProcessor
,在循环依赖场景中,注入的对象将是原始对象而非代理对象,从而导致 AOP 功能失效。
4. 功能的复杂性和设计目标
AsyncAnnotationBeanPostProcessor
:- 其设计目标是简单、专注于异步方法的增强。它不需要处理复杂的依赖注入逻辑,也不需要参与 Spring 的三级缓存机制。
- 通过后处理阶段(
postProcessAfterInitialization
)创建代理,已经足够满足其功能需求。
AbstractAutoProxyCreator
:- 它是 Spring AOP 的核心组件,必须适应各种复杂场景,包括循环依赖、动态代理、BeanFactory 后处理等。
- 实现
SmartInstantiationAwareBeanPostProcessor
是为了更通用地处理代理对象的提前暴露问题。
为什么设计如此
AsyncAnnotationBeanPostProcessor
的设计默认假设使用异步方法的场景通常不会涉及循环依赖。这种假设在大多数实际应用中是成立的:
- 通常,
@Async
的方法会在较独立的组件中定义,而这些组件一般不会与其他 bean 存在复杂的相互依赖关系。 - 异步方法的调用者和被调用者通常是解耦的,调用者通过 Spring 的线程池机制执行异步任务,而不是直接依赖被调用者。
3. 如何解决循环依赖问题
如果在使用 @Async
时确实遇到循环依赖的问题,有几种解决方法:
方法 1: 通过重构解除循环依赖
- 重新设计 bean 的依赖关系,减少相互依赖。
- 将
@Async
方法抽取到一个独立的组件中,确保其与其他依赖分离。
方法 2: 手动提前暴露代理对象
- 使用
@Lazy
注解延迟加载其中一个依赖,避免循环依赖。 - 或者在某些特殊情况下,可以通过自定义
BeanPostProcessor
提前创建代理对象,但这种做法会增加复杂性。
方法 3: 使用其他异步实现
- 如果循环依赖问题无法避免,可以考虑使用其他异步调用机制(如直接使用
ExecutorService
),避免@Async
带来的代理问题。
方法 4: 引入显式的分层设计
- 将异步逻辑和依赖逻辑分离到不同的服务层,减少交叉依赖。