Java @Async注解导致spring启动失败解决方案详解

前言

在这篇文章里,最后总结处,我说了会讲讲循环依赖中,其中一个类添加@Async有可能会导致注入失败而抛异常的情况,今天就分析一下。

一、异常表现,抛出内容

1.1循环依赖的两个class

1.CycleService1

@Service
public class CycleService1 {

	@Autowired
	private CycleService2 cycleService2;

	@WangAnno
	@Async
	public void doThings() {
		System.out.println("it's a async move");
	}

}

2.CycleService2

@Service
public class CycleService2 {

	private CycleService1 cycleService1;

	public void init() {

	}

	@WangAnno
	public void alsoDo() {
		System.out.println("create cycleService2");
	}

}

1.2 启动报错

Bean with name ‘cycleService1' has been injected into other beans [cycleService2] 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.

警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'cycleService1': Bean with name 'cycleService1' has been injected into other beans [cycleService2] 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 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.

Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'cycleService1': Bean with name 'cycleService1' has been injected into other beans [cycleService2] 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 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:654)

at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523)

at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)

at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)

at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:320)

at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)

at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:851)

at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:884)

at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:552)

at com.wang.Test.main(Test.java:109)

二、原因分析

2.1 主要原因

想想我在这篇博客里的图解步骤:

  1. 当Spring在进行bean的实例化的时候,由于CycleService1和CycleService2是循环依赖的,
  2. 同时,由于CycleService1创建早于CycleService2。
  3. 所以,在CycleService1对CycleService2的initializeBean方法执行之后得到了exposedObject,要从二级缓存里获取CycleService1的earlySingletonReference不为null,就需要比较exposedObject和raw CycleService是否还是同一个对象,如果不再是同一个对象,那么就会报错。
  4. 为什么有这个逻辑呢?
  5. 其实是因为如果能从二级缓存里拿出的earlySingletonReference不为null,说明了在该对象再创建过程中被其他对象循环依赖了,且调用了三级工厂中该对象的ObjectFactory方法,基于raw bean生成了对象放入到了二级缓存。但是当raw bean执行完initializeBean之后生成了新的对象,那就出问题了。如下图:

也就是说基于raw bean,得到了两个基于该raw bean生成的proxy对象,Spring容器不知道最终该在容器里保存哪一个了。

2.2 循环依赖放入二级缓存处逻辑

1.每个bean在进行属性注入之前,默认都会往Spring容器中放入一个ObjectFactory进入三级工厂,以便自己在属性注入的时候被循环依赖时调用生成对象

	if (earlySingletonExposure) {
		// 返回一个进行了aop处理的ObjectFactory,提前暴露
		// 但是只有当该实例在创建过程中还被其他实例引用(循环依赖),才会被调用getEarlyBeanReference
		// 此处是第四次调用beanPostProcessor,不一定会调用,只有当该类真的在创建过程中被其他类当做属性所依赖
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}

所以在创建CycleService1过程中,CycleService2去注入CycleService2之前在三级工厂里放入了自己的ObjectFactory对象,然后在CycleService2创建过程中,要注入CycleService1的时候,就会调用Spring容器中的getEarlyBeanReference(beanName, mbd, bean)获取CycleService1,下面我们来看看该方法调用的具体逻辑

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
				}
			}
		}
		return exposedObject;
	}

然后咱们debug发现,只有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);
	}

具体的细节,我们就不进入的,主要就是通过调用wrapIfNecessary生成了raw bean的aop proxy bean,后面放入了二级缓存。

2.3 initializeBean生成的对象

在initializeBean方法里会调用applyBeanPostProcessorsAfterInitialization方法

@Override
	public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
			throws BeansException {

		Object result = existingBean;
		for (BeanPostProcessor processor : getBeanPostProcessors()) {
			Object current = processor.postProcessAfterInitialization(result, beanName);
			if (current == null) {
				return result;
			}
			result = current;
		}
		return result;
	}

在循环里面会调用postProcessAfterInitialization方法
重点关注AbstractAutoProxyCreator的该方法实现:

@Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
				// 对bean进行proxy操作
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}

会发现AbstractAutoProxyCreator#postProcessAfterInitialization里面的具体逻辑就是判断这个类有没有调用过wrapIfNecessary,如果调用过就不再调用,就是保证同一个raw bean不会被多次proxy,同时提前暴露注入到其他对象里的就是proxy bean。
但是由于该bean(CycleService1)上加了@Async注解,此次也会触发AsyncAnnotationBeanPostProcessor#postProcessAfterInitialization,而这个方法,我们在这篇文章里讲过了,正是@Async注解能生效的关键逻辑。所以此处生成了一个具有Async功能的新的async proxy bean

2.4 再次分析原因

基于2.3和2.4,我们基于raw bean得到了二级缓存里的aop proxy bean和async proxy bean。
让我们再回忆一下判断逻辑:

//此处是从二级缓存里面根据beanName拿出对象,因为二级缓存里放入的是因为循环依赖给其他bean注入的代理对象
	Object earlySingletonReference = getSingleton(beanName, false);
	if (earlySingletonReference != null) {
		if (exposedObject == bean) {
			exposedObject = earlySingletonReference;
		}
		// 我们之前早期暴露出去的Bean跟现在最后要放到容器中的Bean不是同一个
		// allowRawInjectionDespiteWrapping为false
		// 并且当前Bean被当成依赖注入到了别的Bean中
		else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
			// 获取到当前Bean依赖的Bean
			String[] dependentBeans = getDependentBeans(beanName);
			// 要得到真实的依赖的Bean
			Set<String> actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
			for (String dependentBean : dependentBeans) {
				// 移除那些仅仅为了类型检查而创建出来
				if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
					actualDependentBeans.add(dependentBean);
				}
			}
			if (!actualDependentBeans.isEmpty()) {
				throw new BeanCurrentlyInCreationException(beanName,
						"Bean with name '" + beanName + "' has been injected into other beans [" +
						StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
						"] 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 " +
						"'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
			}
		}
	}

简而言之,也就是此时从二级缓存里拿到了aop proxy bean,同时了执行完initializeBean之后,raw bean变为了async proxybean,Spring容器基于raw bean得到了两个proxy bean,无法处理了。所以在使用@Async注解时,尽量不要在被循环依赖的Class上添加

解决方案

打破循环依赖

目前我能想到的方法就是打破循环依赖,因为循环依赖发生在bean生命周期的–属性注入阶段,所以我们需要做的就是打破这种循环依赖

1.延迟注入(使用@Lazy注解)

@Service
public class CycleService1 {

	@Lazy
	@Autowired
	private CycleService2 cycleService2;

	@WangAnno
	@Async
	public void doThings() {
		cycleService2.alsoDo();
		System.out.println("it's a async move");
	}

}

看过这篇文章的都知道原理了,此处不再累赘

2. 手动延迟注入(使用applicationContext.getBean)

@Service
public class CycleService1 {

	@Autowired
	private ApplicationContext applicationContext;

	private CycleService2 cycleService2;

	@WangAnno
	@Async
	public void doThings() {
		if (Objects.isNull(cycleService2)) {
			cycleService2 = applicationContext.getBean(CycleService2.class);
		}
		cycleService2.alsoDo();
		System.out.println("it's a async move");
	}

}

其实效果是上面加了@Lazy效果是一样的,不过是我们自己在方法执行的过程中手动进行延迟注入而已。

总结

从二级缓存里拿到earlySingletonReference(aop proxy bean),同时了执行完initializeBean之后,raw bean变为了exposedObject(async proxy bean),Spring容器基于raw bean得到了两个proxy bean,无法处理了。
所以在使用@Async注解时,尽量不要在被循环依赖的Class上添加
实在非要添加,可以看看我给出的解决方法。

到此这篇关于Java @Async注解导致spring启动失败解决方案详解的文章就介绍到这了,更多相关Java @Async注解导致spring启动失败解决方案内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 一篇文章带你入门java注解

    目录 注解 什么是注解 内置注解 元注解 自定义注解 实例 总结 注解 什么是注解 Annotation是从JDK5.0开始引入的新技术 Annotation的作用: 1.不是程序本身,可以对程序做出解释(这一点和注释(comment)没什么区别) 2.可以被其他程序(比如:编译器等)读取 Annotation的格式: ​ 注解是以"@注释名"在代码中存在的,还可以添加一些参数值,例如: ​ @SuppressWarnings(value="unchecked")

  • java中反射和注解的简单使用方法

    目录 什么反射? Java反射机制提供的功能 反射相关的主要API Class 类 获取Class 类的实例( 四种方法) 哪些类型可以有Class 对象? 演示Class类的常用方法 有了Class对象,能做什么? 调用运行时类的指定结构 1. 调用指定方法 关于setAccessible 调用Class对象的newInstance()方法 综合案例: 注解 什么是注解? 常见的Annotation JDK 中的元注解 自定义 Annotation 最后通过反射获取注解信息: 总结 什么反射?

  • Java注解(annotation)简述

    目录 Java注解(annotation)简单上手 1.什么是注解? 2.java内置注解 3.注解的基本运 总结 Java注解(annotation)简单上手 反射reflect:https://www.jb51.net/article/221282.htm 1.什么是注解? 注解就像商场的商品上都贴有自己的标签一样,它提供了关于这个商品的许多额外信息.你可以根据这些信息对其进行附加的处理. "打上标签" 以后,框架就可以利用Java的反射能力,扫描.获取各Class/Method/

  • 如何获取包下所有类中的注解的值(java工具类)

    获取包下所有类中注解的值 作用: 这个工具类主要的作用就是获取类中的注解的值. 应用场景: 做权限的时候获取@RequestMapping();的值,自动添加到数据库中. /** * getRequestMappingValue方法描述: * 作者:thh * 日期:2016年7月18日 下午5:41:00 * 异常对象:@param packageName * 异常对象:@return */ public static List<String> getRequestMappingValue(

  • javax NotBlank和Email注解失效的解决

    javax NotBlank和Email注解失效 使用javax的NotBlan和Email注解, 结果报类似错误 no validator could be found for constraint 'javax.validation.constraints.notblank' 原来是由于javax只提供了注解的定义,未提供对应的处理器,一般使用hibernate提供的注解处理器. 但是hibernate未提供NotBlank和Email注解的处理器(但是hibernate自己定义的NotBl

  • Java @Async注解导致spring启动失败解决方案详解

    前言 在这篇文章里,最后总结处,我说了会讲讲循环依赖中,其中一个类添加@Async有可能会导致注入失败而抛异常的情况,今天就分析一下. 一.异常表现,抛出内容 1.1循环依赖的两个class 1.CycleService1 @Service public class CycleService1 { @Autowired private CycleService2 cycleService2; @WangAnno @Async public void doThings() { System.out

  • Jrebel启动失败解决方案详解

    JRebel是一套JavaEE开发工具.JRebel允许开发团队在有限的时间内完成更多的任务修正更多的问题,发布更高质量的软件产品. 今天使用 jrebel 启动项目的时候,突然啥日志都没有,只有一句Disconnected from the target VM, address: '127.0.0.1:60229', transport: 'socket' 很是莫名其妙.这个时候启动其他项目居然是可以的,说明不是插件的原因,应该是项目问题. 而另一个同学说,他可以启动.我...,难道我是天选之

  • Java集合中的fail-fast(快速失败)机制详解

    简介 我们知道Java中Collection接口下的很多集合都是线程不安全的, 比如 java.util.ArrayList不是线程安全的, 因此如果在使用迭代器的过程中有其他线程修改了list,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略. 这一策略在源码中的实现是通过 modCount 域,modCount 顾名思义就是修改次数,对ArrayList 内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的 exp

  • 深入分析NTFS中文件被锁定导致Process.Start失败的详解

    上周工作中遇到一个奇怪的问题,解决之后想想还是写出来和大家分享一下.故障描述:在A程序中使用Process.Start方法调用一个B.exe的文件时,程序总会自动退出.系统描述:Windows XP Pro SP3尝试过的解决办法:1.b.exe文件是存在的 2.手动执行b.exe是没有任何问题的. 3.a程序调用一个c.exe也是没有问题的. 到此,我基本确认问题出在b.exe上面. 不过由于单独执行b.exe是没有问题的,所以找了半天,一直没解决这个问题.突然间,我注意到手动执行b.exe时

  • 解决因缺少Log4j依赖导致应用启动失败的问题

    前言 最近公司在做版本升级,所有对aaa(指代某个内部依赖)有依赖的应用需要排除掉.从这点看,几乎不会有什么问题,因为仅仅是排除一些maven依赖而已嘛.但是,一位同学在排除依赖的时候,仅仅是把aaa排除了,而没有在测试环境进行测试,在线上发布的时候,日志报dubbo服务注册失败(抛异常和dubbo admin没有看到注册的服务),导致应用启动失败(回滚后正常),影响正常业务5分钟. 事后排查这个问题的时候发现,有两个原因导致了应用启动失败: 去除aaa依赖后,导致应用有多个slf4j的依赖 去

  • Java内部类持有外部类导致内存泄露的原因与解决方案详解

    目录 简介 为什么要持有外部类 实例:持有外部类 实例:不持有外部类 实例:内存泄露 不会内存泄露的方案 简介 说明 本文介绍Java内部类持有外部类导致内存泄露的原因以及其解决方案. 为什么内部类持有外部类会导致内存泄露? 非静态内部类会持有外部类,如果有地方引用了这个非静态内部类,会导致外部类也被引用,垃圾回收时无法回收这个外部类(即使外部类已经没有其他地方在使用了). 解决方案 1.不要让其他的地方持有这个非静态内部类的引用,直接在这个非静态内部类执行业务. 2.将非静态内部类改为静态内部

  • java中Spring Security的实例详解

    java中Spring Security的实例详解 spring security是一个多方面的安全认证框架,提供了基于JavaEE规范的完整的安全认证解决方案.并且可以很好与目前主流的认证框架(如CAS,中央授权系统)集成.使用spring security的初衷是解决不同用户登录不同应用程序的权限问题,说到权限包括两部分:认证和授权.认证是告诉系统你是谁,授权是指知道你是谁后是否有权限访问系统(授权后一般会在服务端创建一个token,之后用这个token进行后续行为的交互). spring

  • Java String index out of range:100错误解决方案详解

    问题出错情况:字符串截取长度,没有那么长的长度所以截取失败. 在这里进行debug之后可以看到,异常在substring中: 也就是判断字符串的时候报错:具体原因就是string字符串indexof的值本身只有5,然后在这里去取其第100 个字符作为截止,因此就会报这个错: 知识点:主要是堆String概念不清.下面针对字符串相关概念做一个简介. 针对上述问题解决办法: 到此这篇关于Java String index out of range:100错误解决方案详解的文章就介绍到这了,更多相关J

  • Java并发编程加锁导致的活跃性问题详解方案

    目录 死锁(Deadlock) 死锁的解决和预防 1.超时释放锁 2.按顺序加锁 3.死锁检测 活锁(Livelock) 避免活锁 饥饿 解决饥饿 性能问题 上下文切换 什么是上下文切换? 减少上下文切换的方法 资源限制 什么是资源限制 资源限制引发的问题 如何解决资源限制的问题 我们主要处理锁带来的问题. 首先就是最出名的死锁 死锁(Deadlock) 什么是死锁 死锁是当线程进入无限期等待状态时发生的情况,因为所请求的锁被另一个线程持有,而另一个线程又等待第一个线程持有的另一个锁 导致互相等

  • Spring循环依赖的解决方案详解

    目录 简介 方案1. Feild注入单例(@AutoWired) 方案2. 构造器注入+@Lazy 方案3. Setter/Field注入单例 方案4. @PostConstruct 方案5. 实现ApplicationContextAware与InitializingBean 简介 说明 本文用实例介绍如何解决Spring的循环依赖问题. 相关网址 Spring循环依赖之问题复现详解 公共代码 package com.example.controller; import com.example

随机推荐