#yyds干货盘点# Spring 源码三千问同样是AOP代理bean,为什么@Async标记的bean循环依赖时会报错?

Posted 老王学源码

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了#yyds干货盘点# Spring 源码三千问同样是AOP代理bean,为什么@Async标记的bean循环依赖时会报错?相关的知识,希望对你有一定的参考价值。

@TOC

前言

前面在分析 哪些循环依赖问题Spring解决不了 时,我们讲过,AOP 代理 bean 在被循环依赖时分两种情况:

  1. 普通的 AOP 代理 bean 被循环依赖,Spring 是支持的
  2. @Async 产生的 AOP 代理 bean 被循环依赖时,Spring 是不支持的,需要通过添加 @Lazy 来解决

那么,为什么同样是 AOP 代理 bean,差别就这么大呢?

版本约定

Spring 5.3.9 (通过 SpringBoot 2.5.3 间接引入的依赖)

正文

先回顾一下 Spring 是如何解决循环依赖的: Spring 是通过三级缓存来解决循环依赖的问题的。

Spring 解决循环依赖核心原理是:
当 beanX 被循环依赖时,这时一级缓存中还没有 beanX,就会通过 beanX 对应的三级缓存 Map<String, ObjectFactory<?>> singletonFactories 来获取 bean 的早期引用。

获取 bean 的早期引用的代码实现如下:

// AbstractAutowireCapableBeanFactory#getEarlyBeanReference()  
/**
 * Obtain a reference for early access to the specified bean, typically for the purpose of resolving a circular reference.
 * 获取指定 bean 的早期引用,通常用于解析循环引用。
 */
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) 
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) 
        for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) 
            exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
        
    
    return exposedObject;

可以看出,通过三级缓存 Map<String, ObjectFactory<?>> singletonFactories 来获取 bean 的早期引用时,如果当前容器中有 SmartInstantiationAwareBeanPostProcessor,那么就通过 SmartInstantiationAwareBeanPostProcessor 来获取 bean 的早期引用。

SmartInstantiationAwareBeanPostProcessor

SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference 只被 AbstractAutoProxyCreator#getEarlyBeanReference 实现了。
代码如下:

// org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#getEarlyBeanReference
public Object getEarlyBeanReference(Object bean, String beanName) 
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
    // 是否需要创建代理类  
    return wrapIfNecessary(bean, beanName, cacheKey);

可以看出它会调用 AbstractAutoProxyCreator#wrapIfNecessary(),也就是 Spring 创建 AOP 代理类时调用的方法。
也就是说,getEarlyBeanReference() 会提前调用 AbstractAutoProxyCreator#wrapIfNecessary() 来生成 AOP 代理类。

这样的话,当普通的 AOP 代理 bean 被循环依赖时,就能被正确的注入属性引用。

非 SmartInstantiationAwareBeanPostProcessor 生产代理的情况

Spring 产生 AOP 代理是在 bean 创建的第三步 initializeBean 的时候,通过 BeanPostProcessor#postProcessAfterInitialization 来产生 AOP 代理类的。
在 Spring 中其实是有两种方式产生 AOP 代理的:

  1. 通过 AbstractAdvisorAutoProxyCreator --> 即: AbstractAutoProxyCreator#wrapIfNecessary()
  2. 通过 AbstractAdvisingBeanPostProcessor

也就是说,Spring 除了使用 AbstractAutoProxyCreator 来产生 AOP 代理 bean 之外,还可以通过 AbstractAdvisingBeanPostProcessor 来产生 AOP 代理 bean。
而 AbstractAdvisingBeanPostProcessor 是没有实现 SmartInstantiationAwareBeanPostProcessor 的。

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware 
    ......


public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor 
    .......

通过 AbstractAdvisingBeanPostProcessor 产生的 AOP 代理 bean 被循环依赖时,通过三级缓存 Map<String, ObjectFactory<?>> singletonFactories 来获取 bean 的早期引用时,就不会提前创建 AOP 代理 bean,也就是拿不到最终暴露到 Spring 容器中的 AOP 代理 bean 的早期引用,这样就会导致这种 AOP 代理 bean 循环依赖注入时的引用不正确。
这种情况是不被允许的,Spring 在 initializeBean 之后,做了 check,检验二级缓存中的 bean 与最终暴露到 Spring 容器中的 bean 是否是相同的,如果不同,就会报错。

而 @Async、@Valid 都是使用 AbstractAdvisingBeanPostProcessor 来产生 AOP 代理的。
综上,@Async、@Valid 产生的 AOP 代理 bean 被循环依赖时,会导致 Spring 容器启动异常,是不支持这种循环依赖的。

小结

在 Spring 中有两种方式产生 AOP 代理:

  1. 通过 AbstractAdvisorAutoProxyCreator,即: AbstractAutoProxyCreator#wrapIfNecessary()
    被用户自定义的 @Aspect 拦截产生的 AOP 代理 bean,都是走这种方式
  2. 通过 AbstractAdvisingBeanPostProcessor
    @Async、@Valid 产生的 AOP 代理 bean 是走这种方式

Spring 支持普通的 AOP 代理 bean 被循环依赖;
Spring 不支持 @Async、@Valid 产生的 AOP 代理 bean 被循环依赖,需要通过添加 @Lazy 来解决


如果本文对你有所帮助,欢迎点赞收藏
有关 Spring 源码方面的问题欢迎留言一起交流...

公众号后台回复:下载IoC 或者 下载AOP 可以免费下载源码测试工程…

阅读更多文章,请关注公众号: 老王学源码


博主好课推荐:

课程 地址
Dubbo源码解读——通向高手之路 https://edu.51cto.com/sd/2e565
正则表达式基础与提升 https://edu.51cto.com/sd/59587

以上是关于#yyds干货盘点# Spring 源码三千问同样是AOP代理bean,为什么@Async标记的bean循环依赖时会报错?的主要内容,如果未能解决你的问题,请参考以下文章

#yyds干货盘点#Spring源码三千问Spring AOP 中 TargetSource 的作用及原理分析

#yyds干货盘点#Spring源码三千问@Lazy延迟加载与延迟注入有什么区别?

#yyds干货盘点# Spring源码三千问Bean的Scope有哪些?scope=request是什么原理?

#yyds干货盘点# Spring源码三千问BeanDefinition详解——什么是 RootBeanDefinition?merged bean definition 又是什么鬼?

#yyds干货盘点# Spring源码三千问Spring动态代理:什么时候使用的 cglib,什么时候使用的是 jdk proxy?

#yyds干货盘点# Spring源码三千问为什么要用三级缓存来解决循环依赖问题?二级缓存行不行?一级缓存行不行?