Spring不能处理的循环依赖

文章目录

      • [场景一:prototype 类型的循环依赖](#场景一:prototype 类型的循环依赖)
      • [场景二: constructor 注入的循环依赖](#场景二: constructor 注入的循环依赖)
      • [场景三:普通的 AOP 代理 Bean 的循环依赖--默认是可以的](#场景三:普通的 AOP 代理 Bean 的循环依赖–默认是可以的)
      • [场景四:@Async 增强的 Bean 的循环依赖](#场景四:@Async 增强的 Bean 的循环依赖)
      • 总结

参考https://blog.csdn.net/wang489687009/article/details/120546430

Spring使用三级缓存解决了循环依赖的问题,但是在某些情况下,它解决不了循环依赖问题,导致项目启动报错。

场景一:prototype 类型的循环依赖

描述:

A --> B --> A,且 A、B 都是 scope=prototype(原型模式

java 复制代码
@Service
@Scope("prototype")
public class A {
    @Autowired
    private B b;
}

@Service
@Scope("prototype")
public class B {
    @Autowired
    private A a;
}

这种场景下会报错:

java 复制代码
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: 
Error creating bean with name 'c1Service': Unsatisfied dependency expressed through field 'c2Service'; 
nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'c2Service': Unsatisfied dependency expressed through field 'c1Service'; 
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'c1Service': Requested bean is currently in creation: Is there an unresolvable circular reference?

分析:

A 实例创建后,populateBean 时,会触发 B 的加载。

B 实例创建后,populateBean 时,会触发 A 的加载。由于 A 的 scope=prototype,从缓存中获取不到 A,要创建一个全新的 A。

这样,就会进入一个死循环。所以Spring提前进行了检查,并抛出了异常。

解决:

在需要循环注入的属性上添加 @Lazy

场景二: constructor 注入的循环依赖

描述: A --> B --> A,且 都是通过构造函数依赖的

java 复制代码
@Service
public class A {
    private B b;
    
    public A(B b) {
        this.b=b;
    }
}

@Service
public class B {
    private A a;
    
    public B(A a) {
        this.a=a;
    }
}

这种场景下会报如下错:

java 复制代码
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: 
Error creating bean with name 'c1' defined in file [/target/classes/com/kvn/beans/circle/constructor/C1.class]: Unsatisfied dependency expressed through constructor parameter 0; 
nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'c2' defined in file [/target/classes/com/kvn/beans/circle/constructor/C2.class]: Unsatisfied dependency expressed through constructor parameter 0; 
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'c1': Requested bean is currently in creation: Is there an unresolvable circular reference?

分析

A 实例在创建时(createBeanInstance),由于是构造注入,这时会触发 B 的加载。

B 实例在创建时(createBeanInstance),又会触发 A 的加载,此时,A 还没有添加到三级缓存中,所以就会创建一个全新的 A。

这样,就会进入一个死循环。所以Spring提前进行了检查,并抛出了异常

解决:

在需要循环注入的属性上添加 @Lazy

例如:

java 复制代码
public A(@Lazy B b){...}

场景三:普通的 AOP 代理 Bean 的循环依赖--默认是可以的

描述:

A --> B --> A, 且 A,B 都是普通的 AOP Proxy 类型的 bean

普通的 AOP proxy 类型指:通过用户自定义的 @Aspect 切面生成的代理 bean,区别于 @Async 标记的类产生的 AOP 代理

Spring 默认解决了 普通的 AOP 代理 Bean 的循环依赖 问题

分析:

通常情况下, AOP proxy 的创建是在 initializeBean 的时候,通过 BeanPostProcessor 处理的。

A 在 createBeanInstance 之后,添加到三级缓存。populateBean 时触发 B 的加载。

B 在 createBeanInstance 之后,添加到三级缓存。populateBean 时触发 A 的加载,这时,三级缓存中有 A,那么通过三级缓存 ObjectFactory#getObject() 可以获取到 bean 的早期引用。

获取 bean 的早期引用的逻辑如下:

java 复制代码
// 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;
}

普通的 AOP 代理都是通过 AbstractAutoProxyCreator 来生成代理类的,而 AbstractAutoProxyCreator 实现了 SmartInstantiationAwareBeanPostProcessor,所以,在通过三级缓存 getEarlyBeanReference() 的时候,就可以提前获取到最终暴露到 Spring 容器中的代理 bean 的早期引用。

java 复制代码
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
        implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
    ......
}

所以,普通的 AOP 代理 bean 的循环依赖是没有问题的。

场景四:@Async 增强的 Bean 的循环依赖

先说结论:

@Async注解标注的bean在循环依赖发生时,不会提前生成代理对象,而是返回原始bean对象给其他bean引用,这时在执行后置处理器逻辑时又会为当前bean生成了一个代理bean对象,最终导致生成的代理bean对象和其他bean所引用的原始bean对象不一致导致报错。

描述:

A --> B --> A, 且 A 是被 @Async 标记的类

java 复制代码
@Service
public class A {
    @Autowired
    private B b;
    
    @Async
    public void m1(){
    }
}

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

这种场景会报如下错:

java 复制代码
Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'p1Service':
Bean with name 'p1Service' has been injected into other beans [p2Service] 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.

分析:

proxy 的创建是在 initializeBean 的时候,通过 BeanPostProcessor 处理的。

A 在 createBeanInstance 之后,添加到三级缓存。populateBean 时触发 B 的加载。

B 在 createBeanInstance 之后,添加到三级缓存。populateBean 时触发 A 的加载,这时,三级缓存中有 A,那么通过三级缓存 ObjectFactory#getObject() 可以获取到 bean 的早期引用。

普通的 AOP 代理都是通过 AbstractAutoProxyCreator 来生成代理类的,AbstractAutoProxyCreator 实现了 SmartInstantiationAwareBeanPostProcessor。
而 @Async 标记的类是通过 AbstractAdvisingBeanPostProcessor 来生成代理的,AbstractAdvisingBeanPostProcessor 没有实现 SmartInstantiationAwareBeanPostProcessor。

java 复制代码
public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor {
    .......
}

所以,这时通过 A 的三级缓存来获取 bean 的早期引用时,获取到的是 bean 的原始对象的引用,而不会提前生成代理对象
这时 B 中注入的 A 对象不是代理对象。最后会导致 B 中持有的 A 对象与 Spring 容器中的 bean A 不是同一个对象。

这种情况显然是有问题的,跟我们的预期是不相符的,所以,Spring 在 initializeBean 之后,检验二级缓存中的 bean 与最终暴露到 Spring 容器中的 bean 是否是相同的,如果不同,就会报错。

综上,@Async 标记的类产生的代理 bean 发生了循环依赖, Spring 默认是解决不了的。

解决:

  • 方式一:在需要循环注入的属性上添加 @Lazy,比如在A类的b字段上加或者在B类的a字段上加都可以
java 复制代码
@Service
public class A {
    @Autowired
    private B b;
    
    @Async
    public void m1(){
    }
}

@Service
public class B {
    @Autowired
    **@Lazy**
    private A a;
}
  • 方式二:在标注@Async注解方法的类上,加上@DependsOn 注解,并且value指定要依赖的属性,依旧可以启动起来,例如下面代码
java 复制代码
@Service
@DependsOn("b")
public class A {

    @Autowired
    private B b;

    @Async
    public void test() {

    }
}

@Service
public class B {

    @Autowired
    private A a;

}

这样就会先去加载B的bean,然后再去加载A的bean,这时候A的bean可以从第三级缓存获取B的bean,然后初始化完成之后返回代理A的bean对象给到B,B也可以初始化完成。。

  • 方式三:在标注@Asnc注解方法的类上加@Lazy注解,例如
java 复制代码
@Service
@Lazy // spring刷新容器时,会判断类上是否有@Lazy注解,有的话就不走getBean流程了,这样就保证了B先去加载,和上面的DependsOn的处理一样
public class A {

    @Autowired
    private B b;

    @Async
    public void test() {

    }
}

@Service
public class B {

		
    @Autowired
    private A a;

}

Order注解用法

一开始我还尝试用Order注解来控制A和B的加载顺序,结果发现行不通,它只能控制bean的初始化顺序

总结

Spring 为我们解决了循环依赖的问题。下面这些情况下,默认是解决不了循环依赖问题的:

  1. prototype 类型的循环依赖
  2. constructor 注入的循环依赖
  3. @Async 类型的 AOP Bean 的循环依赖

这些解决不了的场景都可以通过 @Lazy注解来解决,@Async的场景还可以通过@DependsOn注解解决

相关推荐
JOJO___11 分钟前
Spring MVC 基本配置步骤 总结
java·spring·mvc
一休哥助手15 分钟前
Java/Spring项目中包名以“com”开头的原因分析
java·开发语言·spring
无惧代码2 小时前
实现quartz定时任务控制是否启动 (生产环境 和 开发环境)
java·spring boot·spring
热爱前端的小wen3 小时前
maven的介绍与安装
java·spring·maven·springboot
追风小老头折腾程序3 小时前
Java单体服务和集群分布式SpringCloud微服务的理解
java·后端·spring·spring cloud
你不要在理我了4 小时前
Thinkphp5x远程命令执行 靶场攻略
java·后端·spring
君子剑mango4 小时前
Spring @Import
java·spring
百成Java4 小时前
基于springboot的旅游网站
java·spring boot·后端·mysql·spring·智能家居·旅游
cc7752104 小时前
【常见框架漏洞】ThinkPHP、struts2、Spring、Shiro
java·struts·spring
为java添砖加瓦5 小时前
【读写分离?聊聊Mysql多数据源实现读写分离的几种方案】
java·数据库·spring boot·后端·mysql·spring·mybatis