Spring Bean的循环依赖是Spring框架中一个比较复杂且有趣的话题,涉及到Spring的核心容器、Bean生命周期和依赖注入机制。我将从简入繁地介绍这一概念,确保即使是初学者也能理解。
什么是循环依赖?
在Spring中,循环依赖指的是两个或多个Bean互相持有对方的引用,形成闭环。比如,Bean A依赖于Bean B,同时Bean B又依赖于Bean A。
循环依赖的类型
- 构造器循环依赖:通过构造器注入方式产生的循环依赖。这种依赖是无法解决的,因为在调用构造器之前,Bean就必须被创建。
- 字段注入循环依赖:通过字段(属性)注入方式产生的循环依赖。Spring可以通过三级缓存来解决这种循环依赖。
- 方法注入循环依赖:通过设置方法注入方式产生的循环依赖,同样可以通过三级缓存解决。
Spring是如何解决循环依赖的?
Spring容器解决循环依赖主要依赖于其“三级缓存”机制。这里涉及到Spring容器创建Bean的过程,具体分为以下几步:
- 实例化Bean:首先,Spring容器调用构造函数或工厂方法创建Bean的实例,此时Bean还未初始化(即没有填充属性、没有调用生命周期回调等)。
- 一级缓存:然后,这个原始的Bean实例被放入到一级缓存(singletonObjects)中,此缓存存放的是完成初始化的Bean。
- 二级缓存:如果这个Bean实例有代理增强(如AOP),Spring会创建Bean的代理,并将这个代理放入二级缓存(earlySingletonObjects)中。
- 三级缓存:同时,一个对象工厂被放入三级缓存(singletonFactories),这个工厂负责生成Bean的代理对象。
解决循环依赖的关键点
- 当Bean A创建时,它被加入到三级缓存中。
- 如果Bean A依赖Bean B,在创建Bean B的过程中需要Bean A,此时可以从三级缓存中获取到Bean A的早期引用(可能是代理对象),从而避免了循环依赖的问题。
- 一旦Bean A和Bean B都创建完成,它们就会从三级、二级缓存移动到一级缓存中,此时它们都是完全初始化后的对象。
从简入繁的说明
- 简单理解:循环依赖就像是“我等你,你等我”,但Spring通过一种巧妙的“先给你个空壳(三级缓存的工厂对象),等我弄好了再填充完整内容”来解决这个问题。
- 进阶理解:理解Spring的Bean生命周期,知道Spring如何在创建Bean的过程中使用三级缓存机制来解决字段注入和方法注入产生的循环依赖。
- 深入探讨:深入Spring的源码,了解Spring容器是如何具体实现这一过程的,包括Bean的创建过程、三级缓存的具体作用以及如何在运行时操作这些缓存。
注意
- 构造器注入的循环依赖是无法通过Spring框架解决的,因为在构造函数调用之前就需要依赖的Bean准备好,这在逻辑上是不可能实现的。
- Spring默认支持解决单例作用域Bean的循环依赖,对于原型作用域Bean的循环依赖,Spring不会尝试解决。
以下是具体的实现步骤和代码示例。
1. 创建Spring Boot项目
首先,使用Spring Initializr(https://start.spring.io/)创建一个新的Spring Boot项目,选择Web作为依赖。
2. 定义两个互相依赖的Bean
我们将创建两个类ClassA
和ClassB
,并使它们通过Spring的自动装配特性(@Autowired
注解)互相引用对方。
ClassA.java:
java复制代码
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class ClassA {
private ClassB classB;
@Autowired
public void setClassB(ClassB classB) {
this.classB = classB;
}
public String getMessage() {
return "Message from ClassA";
}
}
ClassB.java:
java复制代码
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class ClassB {
private ClassA classA;
@Autowired
public void setClassA(ClassA classA) {
this.classA = classA;
}
public String getMessage() {
return "Message from ClassB";
}
}
在这个例子中,ClassA
依赖于ClassB
,而ClassB
也依赖于ClassA
。它们通过@Autowired
注解和设置器(setter)方法来实现依赖注入,形成了一个循环依赖。
3. 创建一个测试Controller
为了验证循环依赖是否被成功解决,我们可以创建一个简单的REST控制器,来注入ClassA
并调用其方法。
DemoController.java:
java复制代码
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
private final ClassA classA;
@Autowired
public DemoController(ClassA classA) {
this.classA = classA;
}
@GetMapping("/test")
public String test() {
return classA.getMessage() + " & " + classA.getClassB().getMessage();
}
}
解释
在这个Spring Boot应用中,当Spring容器启动时,它会尝试创建ClassA
和ClassB
的实例。由于使用了字段注入(通过设置器方法),Spring可以通过其内部的循环依赖处理机制来成功实例化这两个互相依赖的Bean。
当请求/test
端点时,DemoController
将返回ClassA
和ClassB
的消息,证明循环依赖被成功解决了。
这个示例展示了Spring是如何处理Bean之间的循环依赖问题,特别是通过字段注入或设置器方法注入。值得注意的是,尽管Spring能够解决单例Bean的循环依赖,但最好还是避免设计出这种相互依赖的情况,以保持代码的清晰和易于管理。