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

createh52个月前 (01-14)技术教程29

循环依赖


?简单来说,就是多个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框架全面详解|Spring快速入门指南

Spring是一个开放源代码的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用。 本课程将深入浅出讲解Spring的核心技术IoC、AOP,剖析框架...

一文读懂:SpringAI - Java 开发的AI智能新利器

使用Spring AI集成国产大模型 到 Java项目指南本文旨在快速介绍如何通过Spring AI让Java项目接入大模型,从而为你的业务增添AI能力。我们将引导您完成从环境准备到代码实现的全过程。...