一文搞懂Spring循环依赖的原理
Spring容器中的循环依赖是指两个或多个Bean彼此之间相互依赖。这种情况下,容器就无法完成Bean实例化,从而导致应用程序无法正常启动。因此,解决循环依赖问题是Spring框架中一个非常重要的问题。
循环依赖的概念
循环依赖是指两个或多个Bean之间出现了相互依赖的情况。例如:Bean1依赖于Bean2,而Bean2又依赖于Bean1。如果没有合理的解决方案,循环依赖会导致Spring容器无法完成Bean的实例化,从而导致应用程序无法正常启动。
Bean的实例化过程
在讲解 Spring的循环依赖之前,我们需要了解一下Spring框架中Bean的实例化过程。简单来说,Bean的实例化过程可以分为以下几个步骤:
- Spring容器扫描应用程序中的所有Bean定义并解析它们的依赖关系。
- 容器按照Bean之间的依赖关系,创建BeanDefinition对象,并将其保存在BeanFactoryRegistry中。
- 容器根据BeanDefinition中的信息,通过实例化工厂来创建Bean实例。
- 对新创建的Bean实例进行属性注入。
- 执行Bean初始化方法。
- 将实例化后的Bean对象放入容器缓存中,供其他Bean使用。
Spring循环依赖的原理
Spring为了解决Bean之间的循环依赖,采用了循环依赖的中断和暂时提前暴露两种解决方案。
循环依赖的中断
循环依赖的中断是指当触发循环依赖条件时,Spring会提前暴露一个对象的早期引用来解决依赖问题。
而早期引用的创建可以分为以下两个阶段:
- 创建一个用于保存对象的半成品
- 进行属性填充
例如:
class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
class B {
private A a;
public B() {
}
public void setA(A a) {
this.a = a;
}
}
当容器创建A的BeanDefinition对象时,发现A依赖于B,容器首先会创建一个只有默认构造函数的半成品B。
在创建B对象的过程中,会首先调用其构造函数,并将未完成的B对象注册到容器的缓存中。在B的半成品对象创建成功后,容器会继续开始创建A对象,并将其注入B对象。此时,B对象的半成品中持有的A对象就是未注入之前的空对象。
接着,容器会创建完整的B对象,该对象中已经可以获取到完整的A对象,并将完整的B对象注入A对象中,最终完成Bean的创建。
暂时提前暴露
如果循环依赖是通过构造函数注入的话,Spring采取的就是暂时提前暴露的方式。
例如:
class A {
private B b;
public A(B b) {
this.b = b;
}
}
class B {
private A a;
public B(A a) {
this.a = a;
}
}
当A和B引用彼此时,Spring无法创建它们的实例。为了解决该问题,Spring通过“暂时提前暴露”的方式来解决该问题。
具体过程如下:
- 容器向构造函数注入B时,Spring会首先创建B的半成品对象。
- 在创建过程中,Spring会检查该半成品对象是否出现了循环依赖。
- 如果检测到了循环依赖,Spring会先将B的半成品对象放到缓存中。
- 等到A的BeanDefinition被解析并注册到容器中时,Spring会利用其来完成B对象的注入。
- 此时,B对象已经完成实例化,并可以被注入到A中,完成双向关联。
示例1
为了更好的理解Spring中循环依赖的问题,下面我们以两个相关联的DAO对象为例来演示它的实现过程。
首先定义两个DAO:
public class UserDaoImpl implements UserDao {
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
}
public class UserServiceImpl implements UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
注意:在UserDaoImpl类中依赖UserService接口,在UserServiceImpl类中依赖UserDao接口。当这两个对象被注入时,就会发现它们之间存在循环依赖。
Spring的循环依赖处理过程如下:
- Spring创建UserDao的BeanDefinition对象,发现它依赖于UserService接口。
- Spring创建UserService的BeanDefinition对象,发现它依赖于UserDao接口。
- 此时,Spring会首先创建一个UserDao的半成品对象。
- 等到UserService的完整Bean创建完成后,Spring会使用它完成UserDao的依赖注入。
- UserService完整Bean创建完成后,Spring开始创建UserDao对象,完成UserService的依赖注入。
示例2
接下来,我们再来看一个使用构造函数注入的例子。
public class B {
private A a;
public B(A a) {
this.a = a;
}
}
public class A {
private B b;
public A(B b) {
this.b = b;
}
}
在上面这个例子中,B依赖于A,A依赖于B。如果使用setter方法来注入依赖关系,那么Spring能够成功解决循环依赖的问题,但使用构造函数注入时,就需要采用“提前暴露”的方式来解决依赖问题。
具体过程如下:
- Spring创建B的BeanDefinition对象并检查依赖关系。
- 发现它依赖于A,因此首先创建A的半成品对象。
- 等到B对象完整Bean创建之后,Spring会使用其完成A的依赖注入。
总结:以上就是解决Spring中循环依赖问题的两种方法,能够有效避免应用程序启动时循环依赖导致的问题。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:一文搞懂Spring循环依赖的原理 - Python技术站