聊一聊Spring中当循环依赖遇到@Async的那点事儿

今天为了弥补上一篇聊一聊在Spring中Bean的循环依赖那些事儿 】遗漏的知识点,特此,今天想聊一聊Spring中当循环依赖遇到@Async的那点事儿,避免童鞋们在工作中再次入坑(坑已踩过)!!!

1、问题背景概述

从上一篇【 聊一聊在Spring中Bean的循环依赖那些事儿 】我们可以基本掌握Spring中Bean之间循环依赖的工作原理,但在工作中,我们时不时会需要引入【异步线程】的概念,从字面上理解分为主线程和子线程

例如:用户成为会员,此时需要用户补充唯一标识身份信息等,但对于后端逻辑来说,主要步骤分为如下几步:

  1. 根据唯一标识存储用户基本信息
  2. 注册成功后给用户发送短信
  3. 注册成功后给用户发放新用户优惠券

问题分析:
有做过类似需求的朋友们会发现,第 2 和 3 步骤需要结合第三方接口才能完成,而调用第三方接口总会存在因网络超时或其他原因,导致程序会执行报错,所以结合需求来说,向第 2 或 3 步骤在执行报错的情况下,是不应该影响主流程的,故此我们可以认为:

  1. 用户注册功能为主流程(即主线程)。
  2. 第 2 或 3 步骤为子流程(即子线程)。
  3. 子流程不能影响主流程。

结合以上问题分析可以得到,我们可以采用异步线程的概念,此处可能举例不是特别的恰当,但我主要是想引入异步线程的概念,方便我们下面继续的深入探究它。

2、异步线程使用

在这里插入图片描述

1、第一步在主启动类上或者新建一个配置类,通过注解@EnableAsync相当于开启异步配置

在这里插入图片描述

2、第二步是在类或方法上标注注解 @Async

在这里插入图片描述

3、自定义配置线程池(根据阿里巴巴编码规范要求),原因也是非常的明确 !!!

3、当循环依赖遇到@Async时程序运行报错

3.1 核心代码部分

@Service
public class A {
    @Autowired
    private B b;
    /**
     * 案例描述:
     */
    @Async // 引发报错的导火索
    public void methodA() {
        System.out.println("方法B执行了...");
        System.out.println(b);
        System.out.println("方法B执行了...");
    }
}

@Service
public class B {
    @Autowired
    private A a;
    /**
     * 案例描述:
     */
    public void methodB() {
        System.out.println("方法A执行了...");
        System.out.println(a);
        System.out.println("方法A执行了...");
    }
}

3.2 报错运行原因

org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Bean with name 'a' has been injected into other beans [b] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:649) ~[spring-beans-5.3.26.jar:5.3.26]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.26.jar:5.3.26]
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.26.jar:5.3.26]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.26.jar:5.3.26]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.26.jar:5.3.26]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.26.jar:5.3.26]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:955) ~[spring-beans-5.3.26.jar:5.3.26]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:920) ~[spring-context-5.3.26.jar:5.3.26]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.26.jar:5.3.26]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.10.jar:2.7.10]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:731) [spring-boot-2.7.10.jar:2.7.10]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) [spring-boot-2.7.10.jar:2.7.10]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:307) [spring-boot-2.7.10.jar:2.7.10]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303) [spring-boot-2.7.10.jar:2.7.10]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292) [spring-boot-2.7.10.jar:2.7.10]
	at com.mrgao.demo.XbqxDemoApplication.main(XbqxDemoApplication.java:24) [classes/:na]

根据代码运行原因,我们可以追踪到报错的地方

在这里插入图片描述

从此处我们可以发现, 仅仅是在原来Bean循环依赖的基础上,增加了一个@Async注解,从而导致Bean创建异常;

3.3 Debug运行调试

在这里插入图片描述

看到这里,有没有小伙伴们仔细的发现到,主要是由于这两个属性值不相等,才会抛出BeanCurrentlyInCreationException异常,因为this.allowRawInjectionDespiteWrapping属性默认为false,且在循环依赖的场景下,必然会抛出BeanCurrentlyInCreationException

4、分析原因

上一篇文章【 聊一聊在Spring中Bean的循环依赖那些事儿 】我也有分析过,主要原因是:AsyncAnnotationBeanPostProcessor不是SmartInstantiationAwareBeanPostProcessor的实例对象,所以从ObjectFactory的getObject()获取对象时,此刻的Bean属于原Bean对象,即就是没有AOP增强后的Bean对象,二级缓存中存的原Bean对象。
另一方面原因: 是在initializeBean的过程中,对象A的方法上恰好存在 @Async 注解(通过AopUtils#canApply() 满足Bean增强的条件),所以在执行完AsyncAnnotationBeanPostProcessor的postProcessAfterInitialization()方法后,得到的是增强后的Bean(即就是增强后的代理Bean对象)
所以根据属性划分为如下:

  1. exposedObject(增强后的Bean)。
  2. earlySingletonReference(二级缓存中的Bean)。
  3. bean(原始bean对象)。
  4. exposedObject 不等于 bean,即就是增强后的对象Bean与原对象bean不相等。

4.1 AsyncAnnotationBeanPostProcessor#postProcessAfterInitialization()

AsyncAnnotationBeanPostProcessor增强Bean的代码执行逻辑如下所示

public Object postProcessAfterInitialization(Object bean, String beanName) {
   	if (this.advisor == null || bean instanceof AopInfrastructureBean) {
   		// 如果当前bean是AopInfrastructureBean的实例对象 或 不存在切面处理时,直接返回
   		return bean;
   	}

   	if (bean instanceof Advised) {
   		// 如果当前bean是Advised的实例对象
   		Advised advised = (Advised) bean;
   		if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
   			// Add our local Advisor to the existing proxy's Advisor chain...
   			if (this.beforeExistingAdvisors) {
   				advised.addAdvisor(0, this.advisor);
   			}
   			else {
   				advised.addAdvisor(this.advisor);
   			}
   			return bean;
   		}
   	}
   	
   	// 核心代码判断 (判断的条件是:类或方法中查看是否存在@Async注解)
   	if (isEligible(bean, beanName)) {
   		// 条件满足 创建代理对象
   		ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
   		if (!proxyFactory.isProxyTargetClass()) {
   			evaluateProxyInterfaces(bean.getClass(), proxyFactory);
   		}
   		proxyFactory.addAdvisor(this.advisor);
   		customizeProxyFactory(proxyFactory);

   		ClassLoader classLoader = getProxyClassLoader();
   		if (classLoader instanceof SmartClassLoader && classLoader != bean.getClass().getClassLoader()) {
   			classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
   		}
   		// 返回代理Bean对象
   		return proxyFactory.getProxy(classLoader);
   	}

   	// 此处表示没有对当前Bean进行代理
   	return bean;
   }

4.2 Spring中如何规避@Async引发的问题呢

在Spring中提供了一种解决方案,就是在执行populateBean()时,之前引发问题的根本原因是:A依赖B且 B依赖A,然后A从三级缓存中得到的不是增强后的Bean,换个角度思考下,就可以认为:在B依赖注入A的时候创建一个增强后的代理Bean,这样是否就可以满足呢?然而Spring中也是这么来做的,那就是通过@Lazy注解标识为当前Bean为懒加载的Bean,直接创建代理对象,在使用的时候才会注册所依赖的Bean

4.3 代码处理逻辑如下

  1. 引用类对象: DefaultListableBeanFactory#resolveDependency()
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
			@Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

		descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
		if (Optional.class == descriptor.getDependencyType()) {
			return createOptionalDependency(descriptor, requestingBeanName);
		}
		else if (ObjectFactory.class == descriptor.getDependencyType() ||
				ObjectProvider.class == descriptor.getDependencyType()) {
			return new DependencyObjectProvider(descriptor, requestingBeanName);
		}
		else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
			return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
		}
		else {
			// @Lazy核心处理逻辑
			Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
					descriptor, requestingBeanName);
			// 如果不为空时,则可以跳过执行doResolveDependency(),若是三级缓存开启时,后面会从三级获取代理对象的
			if (result == null) {
				result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
			}
			// 返回result
			return result;
		}
	}
public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
		return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null);
	}
4.3.1 判断@Lazy代码逻辑
protected boolean isLazy(DependencyDescriptor descriptor) {
		// 先从类上获取@Lazy
		for (Annotation ann : descriptor.getAnnotations()) {
			Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class);
			if (lazy != null && lazy.value()) {
				return true;
			}
		}
		// 再从方法上获取@Lazy
		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;
	}
4.3.2 创建代理Bean逻辑

当调用目标代理Bean时,会监听 DynamicAdvisedInterceptor#intercept()。

protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) {
		BeanFactory beanFactory = getBeanFactory();
		Assert.state(beanFactory instanceof DefaultListableBeanFactory,
				"BeanFactory needs to be a DefaultListableBeanFactory");
		final DefaultListableBeanFactory dlbf = (DefaultListableBeanFactory) beanFactory;

		TargetSource ts = new TargetSource() {
			@Override
			public Class<?> getTargetClass() {
				return descriptor.getDependencyType();
			}
			@Override
			public boolean isStatic() {
				return false;
			}
			@Override
			public Object getTarget() {
				// 注意:当执行调用时会触发此处
				Set<String> autowiredBeanNames = (beanName != null ? new LinkedHashSet<>(1) : null);
				// 从容器中获取目标Bean对象
				Object target = dlbf.doResolveDependency(descriptor, beanName, autowiredBeanNames, 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();
					}
					throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(),
							"Optional dependency not present for lazy injection point");
				}
				if (autowiredBeanNames != null) {
					for (String autowiredBeanName : autowiredBeanNames) {
						if (dlbf.containsBean(autowiredBeanName)) {
							// 注册所依赖的bean
							dlbf.registerDependentBean(autowiredBeanName, beanName);
						}
					}
				}
				// 返回目标bean
				return target;
			}
			@Override
			public void releaseTarget(Object target) {
			}
		};
		
		// 创建代理Bean
		ProxyFactory pf = new ProxyFactory();
		pf.setTargetSource(ts);
		Class<?> dependencyType = descriptor.getDependencyType();
		if (dependencyType.isInterface()) {
			pf.addInterface(dependencyType);
		}
		// 返回代理Bean
		return pf.getProxy(dlbf.getBeanClassLoader());
	}

4.4 小结

综上所述,我们已经大致清楚@Async和Spring循环依赖的工作原理,可能有些地方需要童鞋们自己Debug调试跟踪下,这样可能理解的更快些,那么我们在工作中如何快速的避免这种问题呢,我认为可以通过以下两点进行优化。

  1. 结合Spring官方来分析,Spring中默认不开启循环依赖,间接的告知我们应减少使用Bean之间的循环依赖。
  2. 如果工作中,不得不使用循环依赖时,其中之一的解决的方法是通过@Lazy注解。另外一种解决方法是增加一层增加一层调用:例如 A和B互相循环依赖彼此,而A需要增加@Async,那么换一种思路,我们可以新增一层C ,C增加@Async,然后 C再调用A,总之就是避免@Async和循环依赖二者都存在的情况)

5、疑问点

看到这里,童鞋们是不是不由心里产生了疑惑呢?那就是AOP切面遇到循环依赖为什么不会报上边那个BeanCurrentlyInCreationException创建异常呢?其工作原理都是生成代理Bean对象,为什么AOP切面是支持的,@Async却是不支持的。

最后,如果我上述表达有错误的地方,麻烦童鞋们能及时私信我,希望咱们能一起进步! 也希望童鞋们能够更多的支持与点赞哦,对于本文存在的疑问点,下一篇我会篇文章,来为童鞋们解决疑惑(本文先暂留个悬念)。在这里插入图片描述

  • 15
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值