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注解解决

相关推荐
CodeAmaz1 小时前
Spring循环依赖与三级缓存详解
spring·循环依赖·三级缓存
diudiu96283 小时前
Maven配置阿里云镜像
java·spring·阿里云·servlet·eclipse·tomcat·maven
222you6 小时前
SpringAOP的介绍和入门
java·开发语言·spring
CodeAmaz7 小时前
Spring编程式事务详解
java·数据库·spring
谷哥的小弟7 小时前
Spring Framework源码解析——RequestContext
java·后端·spring·框架·源码
程序员阿鹏8 小时前
SpringBoot自动装配原理
java·开发语言·spring boot·后端·spring·tomcat·maven
老华带你飞8 小时前
工会管理|基于springboot 工会管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·spring
⑩-9 小时前
SpringCloud-Feign客户端实战
后端·spring·spring cloud
qq_124987075310 小时前
基于springboot的幼儿园家校联动小程序的设计与实现(源码+论文+部署+安装)
java·spring boot·后端·spring·微信小程序·小程序
后端小张10 小时前
【Java 进阶】深入理解Redis:从基础应用到进阶实践全解析
java·开发语言·数据库·spring boot·redis·spring·缓存