- 不要在循环引用的bean里使用@async。2. 使用@Lazy解决循环依赖导致无法初始化的问题
本质上:@Async的后置处理器,没有在getEarlyRefrence时,创建被@Async修饰代理对象,而是在initlizeBean方法中创建了代理对象。作为对比,被@Aspect修饰的Aop对象会在getEarlyRefrence阶段,提前创建代理对象,顾aop时不存在该问题
1、前奏:我在distPlanService中定义了一个异步方法,采用@Async注解。
2、异常:org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'deviceParamsManagerController': Unsatisfied dependency expressed through field 'deviceParamsManagerService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'deviceParamsManagerService': Unsatisfied dependency expressed through field 'deviceParamsValueManagerService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'deviceParamsValueManagerService': Unsatisfied dependency expressed through field 'remoteControlService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'remoteControlServiceImpl': Unsatisfied dependency expressed through field 'distPlanService'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'distPlanService': Bean with name 'distPlanService' has been injected into other beans [versionSpPackageService,distPlanTaskServiceImpl,adUpgradeService,versionMainService,adStrategyPackageService] 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.
3、看第2点,搜索distPlanService关键字看红色比标注,解释是:创建名称为“distPlanService”的 bean 时出错:名称为“distPlanService”的 Bean 已作为循环引用的一部分被注入到其原始版本中的其他 bean [versionSpPackageService,distPlanTaskServiceImpl,adUpgradeService,versionMainService,adStrategyPackageService]
4、在看下图:
如此就找到了原因:distPlanService和versionSpPackageService有互相注入(@Autowired)
5、@Async解释:
spring从3开始提供了@Async注解,此注解可以被标注在方法或类上。案例中,标注在了方法上,以此代表该方法在被调用者调用时无语等待该方法的返回而继续执行调用处以下的代码逻辑,异步方法的实际执行将提交给Spring TaskExecutor的任务中,由指定的线程池中的线程执行。
6、循环依赖:
7、解释:
如果B(versionSpPackageService)注入了A(distPlanService),A注入了B, 此时A中增加一个异步方法(被@Async标注的方法),此时重启项目导致报错:前因后果,需要去分析源码,这里我用别人总结的话简单说一下:
1.context.getBean(A)开始创建A,A实例化完成后给A的依赖属性b开始赋值
2.context.getBean(B)开始创建B,B实例化完成后给B的依赖属性a开始赋值
3.重点:此时因为A支持循环依赖,所以会执行A的getEarlyBeanReference方法得到它的早期引用。而执行getEarlyBeanReference()的时候因为@Async根本还没执行,所以最终返回的仍旧是原始对象的地址
4.B完成初始化、完成属性的赋值,此时属性field持有的是Bean A原始类型的引用
5.完成了A的属性的赋值(此时已持有B的实例的引用),继续执行初始化方法initializeBean(...),在此处会解析@Aysnc注解,从而生成一个代理对象,所以最终exposedObject是一个代理对象(而非原始对象)最终加入到容器里
6.尴尬场面出现了:B引用的属性A是个原始对象,而此处准备return的实例A竟然是个代理对象,也就是说B引用的并非是最终对象(不是最终放进容器里的对象)
7.执行自检程序:由于allowRawInjectionDespiteWrapping默认值是false,表示不允许上面不一致的情况发生,最终就抛错了。
8、我的解决方法:
(1)重新建class,把@Async的方法放在新的类中,从根本上消除循环依赖
(2) 也有其他方法,我并没有去验证:例如:采用@Lazy或者使用setter注入:在A注入B
private VersionSpPackageService b;
public void setB(VersionSpPackageService b) {
this.b = b;
}
B注入A也是如此。