Spring处理@Async导致的循环依赖失败问题的方案详解

目录
  • 简介
  • 问题复现
  • 原因分析
  • 解决方案
    • 方案1:懒加载
    • 方案2:不让@Async的类有循环依赖
    • 方案3:allowRawInjectionDespiteWrapping设置为true
  • 为什么@Transactional不会导致失败

简介

说明

本文介绍SpringBoot中的@Async导致循环依赖失败的原因及其解决方案。

概述

我们知道,Spring解决了循环依赖问题,但Spring的异步(@Async)会使得循环依赖失败。本文将用实例来介绍其原因和解决方案。

问题复现

启动类

启动类添加@EnableAsync以启用异步功能。

package com.knife;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@EnableAsync
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

Service

A

package com.knife.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
public class A {
    @Autowired
    private B b;

    @Async
    public void print() {
        System.out.println("Hello World");
    }
}

B

package com.knife.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class B {
    @Autowired
    private A a;
}

Controller

package com.knife.controller;

import com.knife.service.A;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @Autowired
    private A a;

    @GetMapping("/test")
    public String test() {
        a.print();
        return "test success";
    }
}

启动:(报错)

Caused by: 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:624) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1306) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1226) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
    ... 20 common frames omitted

原因分析

@EnableAsync开启时向容器内注入AsyncAnnotationBeanPostProcessor,它是一个BeanPostProcessor,实现了postProcessAfterInitialization方法。创建代理的动作在抽象父类AbstractAdvisingBeanPostProcessor上:

    // 这个方法主要是为有@Async 注解的 bean 生成代理对象
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (this.advisor == null || bean instanceof AopInfrastructureBean) {
            // Ignore AOP infrastructure such as scoped proxies.
            return bean;
        }

        // 如果此Bean已经被代理了(比如已经被事务那边给代理了~~)
        if (bean instanceof Advised) {
            Advised advised = (Advised) bean;

            if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
                // Add our local Advisor to the existing proxy's Advisor chain...
                // beforeExistingAdvisors决定这该advisor最先执行还是最后执行
                // 此处的advisor为:AsyncAnnotationAdvisor  它切入Class和Method标注有@Aysnc注解的地方~~~
                if (this.beforeExistingAdvisors) {
                    advised.addAdvisor(0, this.advisor);
                } else {
                    advised.addAdvisor(this.advisor);
                }
                return bean;
            }
        }
        // 若不是代理对象,则进行处理
        if (isEligible(bean, beanName)) {
            //copy属性 proxyFactory.copyFrom(this); 工厂模式生成一个新的 ProxyFactory
            ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
            // 如果没有采用CGLIB,就去探测它的接口
            if (!proxyFactory.isProxyTargetClass()) {
                evaluateProxyInterfaces(bean.getClass(), proxyFactory);
            }
            // 切入切面并创建一个getProxy 代理对象
            proxyFactory.addAdvisor(this.advisor);
            customizeProxyFactory(proxyFactory);
            return proxyFactory.getProxy(getProxyClassLoader());
        }

        // No proxy needed.
        return bean;
    }

    protected boolean isEligible(Object bean, String beanName) {
        return isEligible(bean.getClass());
    }

    protected boolean isEligible(Class<?> targetClass) {
        //首次从 eligibleBeans 这个 map 中获取值肯定为 null
        Boolean eligible = this.eligibleBeans.get(targetClass);
        if (eligible != null) {
            return eligible;
        }
        //如果没有配置 advisor(即:切面),返回 false
        if (this.advisor == null) {
            return false;
        }

        // 若类或方法有 @Aysnc 注解,AopUtils.canApply 会判断为 true
        eligible = AopUtils.canApply(this.advisor, targetClass);
        this.eligibleBeans.put(targetClass, eligible);
        return eligible;
    }
  1. 创建A,A实例化完成后将自己放入第三级缓存,然后给A的依赖属性b赋值
  2. 创建B,B实例化后给B的依赖属性a赋值
  3. 从第三级缓存中获得A(执行A的getEarlyBeanReference方法)。执行getEarlyBeanReference()时@Async根本还被扫描,所以返回的是原始类型地址(没被代理的对象地址)。
  4. B完成初始化、属性的赋值,此时持有A原始类型引用(没被代理)
  5. 完成A的属性的赋值(此时持有B的引用),继续执行初始化方法initializeBean(...),解析@Aysnc注解,生成一个代理对象,exposedObject是一个代理对象(而非原始对象),加入到容器里。
  6. 问题出现了:B的属性A是个原始对象,而此处的实例A却是个代理对象。(即:B里的A不是最终对象(不是最终放进容器的对象))
  7. 执行自检程序:由于allowRawInjectionDespiteWrapping默认值是false,表示不允许上面不一致的情况发生,就报错了

解决方案

有三种方案:

懒加载:使用@Lazy或者@ComponentScan(lazyInit = true)

不要让@Async的Bean参与循环依赖

将allowRawInjectionDespiteWrapping设置为true

方案1:懒加载

说明

建议使用@Lazy。

不建议使用@ComponentScan(lazyInit = true),因为它是全局的,容易产生误伤。

实例

这两个方法都是可以的:

  • 法1. 将@Lazy放到A类的b成员上边
  • 法2: 将@Lazy放到B类的a成员上边

法1:将@Lazy放到A类的b成员上边

package com.knife.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
public class A {
    @Lazy
    @Autowired
    private B b;

    @Async
    public void print() {
        System.out.println("Hello World");
    }
}

法2:将@Lazy放到B类的a成员上边

package com.knife.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

@Component
public class B {
    @Lazy
    @Autowired
    private A a;
}

这样启动就能成功。

原理分析

以这种写法为例进行分析:@Lazy放到A类的b成员上边。

即:

package com.knife.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

@Component
public class A {
    @Lazy
    @Autowired
    private B b;

    @Async
    public void print() {
        System.out.println("Hello World");
    }
}

假设 A 先加载,在创建 A 的实例时,会触发依赖属性 B 的加载,在加载 B 时发现它是一个被 @Lazy 标记过的属性。那么就不会去直接加载 B,而是产生一个代理对象注入到了 A 中,这样 A 就能正常的初始化完成放入一级缓存了。

B 加载时,将前边生成的B代理对象取出,再注入 A 就能直接从一级缓存中获取到 A,这样 B 也能正常初始化完成了。所以,循环依赖的问题就解决了。

方案2:不让@Async的类有循环依赖

略。

方案3:allowRawInjectionDespiteWrapping设置为true

说明

本方法不建议使用。

这样配置后,容器启动不报错了。但是:Bean A的@Aysnc方法不起作用了。因为Bean B里面依赖的a是个原始对象,所以它不能执行异步操作(即使容器内的a是个代理对象)。

方法

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
import org.springframework.stereotype.Component;

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true);
    }
}

为什么@Transactional不会导致失败

概述

同为创建动态代理对象,同为一个注解标注在类/方法上,为何@Transactional就不会出现这种启动报错呢?

原因是,它们代理的创建的方式不同:

@Transactional创建代理的方式:使用自动代理创建器InfrastructureAdvisorAutoProxyCreator(AbstractAutoProxyCreator的子类),它实现了getEarlyBeanReference()方法从而很好的对循环依赖提供了支持

@Async创建代理的方式:使用AsyncAnnotationBeanPostProcessor单独的后置处理器。它只在一处postProcessAfterInitialization()实现了对代理对象的创建,因此若它被循环依赖了,就会报错

详解

处理@Transactional注解的是InfrastructureAdvisorAutoProxyCreator,它是SmartInstantiationAwareBeanPostProcessor的子类。AbstractAutoProxyCreator对SmartInstantiationAwareBeanPostProcessor的getEarlyBeanReference方法进行了覆写:

AbstractAutoProxyCreator# getEarlyBeanReference

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {

    // 其他代码

    @Override
    public Object getEarlyBeanReference(Object bean, String beanName) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        this.earlyProxyReferences.put(cacheKey, bean);
        return wrapIfNecessary(bean, beanName, cacheKey);
    }

}

AbstractAutoProxyCreator#postProcessAfterInitialization方法中,判断是否代理过,是的话,直接返回:

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {

    // 其他代码

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

}

以上就是Spring处理@Async导致的循环依赖失败问题的方案详解的详细内容,更多关于Spring 循环依赖失败的资料请关注我们其它相关文章!

(0)

相关推荐

  • 深度解析SpringBoot中@Async引起的循环依赖

    目录 事故时间线 猜想 什么是循环依赖 什么是@Async 啊,昨晚发版又出现了让有头大的循环依赖问题,按理说Spring会为我们解决循环依赖,但是为什么还会出现这个问题呢?为什么在本地.UAT以及PRE环境都没有出现这个问题,但是到了PROD环境就出现了这个问题呢?本文将从事故时间线.及时止损.复盘分析等几个方面为大家带来详细的分析,干货满满! 事故时间线 本着"先止损.后复盘分析"的原则,我们来看一下这次发版事故的时间线. 2021年11月16日晚23点00分00秒开始发版,此时集

  • Java Spring注解之@Async的基本用法和示例

    目录 背景 异步调用 @Async介绍 在Spring中启用@Async 示例一:基本使用方式 示例二:在同一个类中调用异步方法 示例三:异步方法是static方法 示例四:在方法级别上修改默认的执行器 补充:Java中异步注解@Async的陷阱 总结 背景 通常,在Java中的方法调用都是同步调用,比如在A方法中调用了B方法,则在A调用B方法之后,必须等待B方法执行并返回后,A方法才可以继续往下执行.这样容易出现的一个问题就是如果B方法执行时间较长,则可能会导致调用A的请求响应迟缓,为了解决这

  • springboot @Async 注解如何实现方法异步

    目录 @Async注解如何实现方法异步 一.springboot的App类需要的注解 二.service层的注解 三.调用层 异步注解@Async的使用以及注意事项 第一步开启异步 下面显示配置线程的代码实现 使用@Async导致异步不成功的情况 @Async注解如何实现方法异步 处理大批量数据的时候,效率很慢.所以考虑一下使用多线程. 刚开始自己手写的一套,用了线程池启动固定的线程数进行跑批.但是后来老大考虑到自己手写的风险不好控制,所以使用spring的方法. 这里没有详细介绍,只有简单的d

  • 关于SpringBoot使用@Async的总结

    目录 SpringBoot使用@Async总结 如下方式会使@Async失效 SpringBoot实现异步(Async)接口 1. 启动类引入@EnableAsync注解 2. 建立异步任务类 3. 建立测试接口 SpringBoot使用@Async总结 注意事项: 如下方式会使@Async失效 1. 异步方法使用static修饰 2. 异步类没有使用@Component注解(或其他注解)导致spring无法扫描到异步类 3. 异步方法不能与异步方法在同一个类中 4. 类中需要使用@Autowi

  • java SpringBoot注解@Async不生效的解决方法

    目录 问题描述: 解决方案: 总结: SpringBoot 注解@Async不生效的解决方法 问题描述: 这里虽然加了@EnableAsync和@Async,但是异步请求依然没有生效 解决方案: 方法一: 同一个类中调用需要先获取代理对象,也就是手动获取对象 @Service @EnableAsync public class DemoService { public void add(){ DemoService bean = SpringUtil.getBean(DemoService.cl

  • Java Spring之@Async原理案例详解

    目录 前言 一.如何使用@Async 二.源码解读 总结 前言 用过Spring的人多多少少也都用过@Async注解,至于作用嘛,看注解名,大概能猜出来,就是在方法执行的时候进行异步执行. 一.如何使用@Async 使用@Async注解主要分两步: 1.在配置类上添加@EnableAsync注解 @ComponentScan(value = "com.wang") @Configuration @EnableAsync public class AppConfig { } 2.在想要异

  • Spring处理@Async导致的循环依赖失败问题的方案详解

    目录 简介 问题复现 原因分析 解决方案 方案1:懒加载 方案2:不让@Async的类有循环依赖 方案3:allowRawInjectionDespiteWrapping设置为true 为什么@Transactional不会导致失败 简介 说明 本文介绍SpringBoot中的@Async导致循环依赖失败的原因及其解决方案. 概述 我们知道,Spring解决了循环依赖问题,但Spring的异步(@Async)会使得循环依赖失败.本文将用实例来介绍其原因和解决方案. 问题复现 启动类 启动类添加@

  • Spring使用三级缓存解决循环依赖的问题

    Spring如何使用三级缓存解决循环依赖在没开始文章之前首先来了解一下什么是循环依赖 @Component public class A { @Autowired B b; } @Component public class B { @Autowired A a; } 在对象A创建过程中,需要注入B,因为容器中没有B,则去创建B,B创建过程中又需要注入A,而A在等待B的创建,B在等待A的创建,导致两者都无法创建成功,无法加入到单例池供用户使用. Spring则通过三级缓存来解决循环依赖的问题,另

  • 你知道怎么用Spring的三级缓存解决循环依赖吗

    目录 1. 前言 2. Spring Bean的循环依赖 3. Spring中三大循环依赖场景演示 3.1 构造器注入循环依赖 3.2 singleton模式field属性注入循环依赖 3.3 prototype模式field属性注入循环依赖 4. Spring解决循环依赖的原理分析 4.1 Spring创建Bean的流程 4.2 Spring容器的“三级缓存” 4.3 源码解析 4.4 流程总结 5. 总结 1. 前言 循环依赖:就是N个类循环(嵌套)引用. 通俗的讲就是N个Bean互相引用对

  • spring如何快速稳定解决循环依赖问题

    循环依赖其实就是循环引用,很多地方都说需要两个或则两个以上的bean互相持有对方最终形成闭环才是循环依赖,比如A依赖于B,B依赖于C,C又依赖于A.其实一个bean持有自己类型的属性也会产生循环依赖. setter singleton循环依赖 使用 SingleSetterBeanA依赖SingleSetterBeanB,SingleSetterBeanB依赖SingleSetterBeanA. @Data public class SingleSetterBeanA { @Autowired

  • Spring源码解析之循环依赖的实现流程

    目录 前言 循环依赖实现流程 前言 上篇文章中我们分析完了Spring中Bean的实例化过程,但是没有对循环依赖的问题进行分析,这篇文章中我们来看一下spring是如何解决循环依赖的实现. 之前在讲spring的过程中,我们提到了一个spring的单例池singletonObjects,用于存放创建好的bean,也提到过这个Map也可以说是狭义上的spring容器. private final Map<String, Object> singletonObjects = new Concurr

  • Spring拦截器中注入Bean失败解放方案详解

    目录 简介 问题重现 解决方案 简介 说明 本文用示例介绍如何解决拦截器中注入Bean失败的问题. 场景 Token拦截器中需要用@Autowired注入JavaJwtUtil类,结果发现注入的JavaJwtUtil为Null. 原因 拦截器的配置类是以new JwtInterceptor的方式使用的,那么这个JwtInterceptor不受Spring管理.因此,里边@Autowired注入JavaJwtUtil是不会注入进去的. 问题重现 代码 application.yml server:

  • Spring Boot实现数据访问计数器方案详解

    目录 1.数据访问计数器 2.代码实现 2.1.方案说明 2.2.代码 2.3.调用 1.数据访问计数器   在Spring Boot项目中,有时需要数据访问计数器.大致有下列三种情形: 1)纯计数:如登录的密码错误计数,超过门限N次,则表示计数器满,此时可进行下一步处理,如锁定该账户. 2)时间滑动窗口:设窗口宽度为T,如果窗口中尾帧时间与首帧时间差大于T,则表示计数器满.   例如使用redis缓存时,使用key查询redis中数据,如果有此key数据,则返回对象数据:如无此key数据,则查

  • Java中Spring Boot+Socket实现与html页面的长连接实例详解

    Spring Boot+Socket实现与html页面的长连接,客户端给服务器端发消息,服务器给客户端轮询发送消息,附案例源码 功能介绍 客户端给所有在线用户发送消息客户端给指定在线用户发送消息服务器给客户端发送消息(轮询方式) 注意:socket只是实现一些简单的功能,具体的还需根据自身情况,代码稍微改造下 项目搭建 项目结构图 pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xml

  • Spring中bean的初始化和销毁几种实现方式详解

    Bean的生命周期 : 创建bean对象 – 属性赋值 – 初始化方法调用前的操作 – 初始化方法 – 初始化方法调用后的操作 – --- 销毁前操作 – 销毁方法的调用. [1]init-method和destroy-method 自定义初始化方法和销毁方法两种方式:xml配置和注解. ① xml配置 <bean id="person" class="com.core.Person" scope="singleton" init-meth

随机推荐