走进舒适圈の小窝

Loading...

Java框架 —— Spring(循环依赖问题)

循环依赖


 简单来说,就是多个bean之间相互依赖,最终形成闭环,极限情况,甚至可能出现自己依赖自己

@Component
public class A {
// A中注入了B
@Autowired
private B b;
}
@Component
public class B {
// B中也注入了A
@Autowired
private A a;
}

循环依赖发生的场景

  1. 通过构造器注入循环依赖(无法解决)

当Bean通过构造器注入相互依赖时,Spring无法解决这种循环依赖,因为在构造器调用之前,Bean尚未创建,无法注入。

@Component
public class Person {
public Person(User user) {}
}
@Component
public class User {
public User(Person person){}
}

项目中如果出现此类代码,会抛出异常BeanCurrentlyInCreationException

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
  1. 通过属性注入循环依赖(可以解决)

通过字段(field)或setter方法注入依赖时,Spring可以通过三级缓存来解决循环依赖问题。

@Component
public class User {
@Autowired
Person person;
}
@Component
public class Person {
@Autowired
User user;
}

项目可以正常运行

  1. 多例Bean的属性注入循环依赖(无法解决)

对于多例(prototype)作用域的Bean,Spring默认不会在启动时初始化,而是在使用时才初始化,因此可能不会立即出现循环依赖问题,但在实际使用中可能会遇到。

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class User {
@Autowired
Person person;
}
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class Person {
@Autowired
User user;
}

项目启动时,不会立即抛异常,因为多例的只有在被调用时,才会进行初始化,当被调用时就会抛异常

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'mytest.TestSpringBean': Unsatisfied dependency expressed through field 'a'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a': Unsatisfied dependency expressed through field 'b'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:596)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:374)

Spring容器的三级缓存


  • 一级缓存:存放完全初始化,且属性赋值完毕的Bean,可以直接使用

  • 二级缓存:存放早期Bean的引用,即已实例化但是未装配属性的Bean

  • 三级缓存:存放Bean工厂,即实例化Bean的工厂对象,用于解决循环依赖中Bean属性未完全装配的问题

解决循环依赖的步骤

 Spring在实例化Bean时,会先创建一个空的Bean对象,并将其放入三级缓存中,Spring开始对Bean进行属性赋值,如果发现循环依赖,会通过工厂方法提前暴露一个原始的Bean实例,并将其放入二级缓存,这样,当其他Bean引用该Bean时,可以使用这个原始实例,避免了循环依赖

简单的循环依赖(没有AOP)

结合了AOP的循环依赖

对A进行了AOP代理的话,此时getEarlyBeanReference将返回一个代理后的对象,而不是实例化阶段创建的对象


三级缓存为什么要使用工厂而不是直接使用引用?换而言之,为什么需要这个三级缓存,直接通过二级缓存暴露一个引用不行吗?

答:这个工厂的目的在于延迟对实例化阶段生成的对象的代理,只有真正发生循环依赖的时候,才去提前生成代理对象,否则只会创建一个工厂并将其放入到三级缓存中,但是不会去通过这个工厂去真正创建对象

总结

Spring 如何解决循环依赖

 Spring通过三级缓存去解决,一级缓存为单例池(singletonObjects),二级缓存为早期曝光对象(earlySingletonObjects),三级缓存为早期曝光对象工厂(singletonFactories)。

 当A、B两个类发生循环依赖时,在A完成实例化后,就使用实例化后的对象创建一个对象工厂,并添加到三级缓存中,如果A被AOP代理,那么通过这个工厂获取到的是A代理后的对象;如果A没有被AOP代理,那么这个工厂获取到的就是A的实例化对象。

 当A进行属性注入时,会去创建B,同时B又依赖于A,所以创建B的同时又会调用getBean(a)来获取需要的依赖,此时getBean(a)会从缓存中获取:第一步,先获取三级缓存中的工厂;第二步,调用对象工厂的getObject方法来获取对应对象,得到这个对象后,将其注入到B中,然后B走完它的生命周期,当B创建完后,会将B再注入到A中,此时,A在完成它的生命周期。至此,循环依赖结束


为什么要使用三级缓存呢?二级缓存能解决循环依赖吗

 如果要使用二级缓存解决循环依赖,意味着所有Bean在实例化后就要完成AOP代理,这样违背了Spring设计的原则,Spring在设计之初就是通过AnnotationAwareAspectJAutoProxyCreator这个后置处理器来在Bean生命周期的最后一步来完成AOP代理,而不是在实例化后就立马进行AOP代理。





参考文章:面试必杀技,讲一讲Spring中的循环依赖

posted @   走进舒适圈  阅读(228)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示