Spring循环依赖的原理是什么

发布时间:2021-07-24 13:51:03 作者:Leah
来源:亿速云 阅读:268
# Spring循环依赖的原理是什么

## 前言

在Spring框架的使用过程中,开发者经常会遇到**循环依赖(Circular Dependency)**的问题。所谓循环依赖,是指两个或多个Bean相互依赖,形成一个闭环。例如,Bean A依赖Bean B,而Bean B又依赖Bean A。这种情况下,Spring是如何处理的?其背后的原理是什么?本文将深入探讨Spring循环依赖的解决机制,从源码层面分析其实现原理,并讨论在实际开发中如何避免潜在问题。

---

## 一、什么是循环依赖

### 1.1 循环依赖的定义
循环依赖是指两个或多个组件(在Spring中即Bean)之间直接或间接地相互依赖,形成一个闭环。常见的循环依赖场景包括:
- **构造器循环依赖**:通过构造函数相互注入。
- **属性循环依赖**:通过Setter方法或字段注入相互依赖。

### 1.2 循环依赖的示例
以下是一个典型的属性循环依赖代码:
```java
@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
}

@Service
public class ServiceB {
    @Autowired
    private ServiceA serviceA;
}

此时,ServiceA依赖ServiceB,而ServiceB又依赖ServiceA,形成循环依赖。


二、Spring如何处理循环依赖

2.1 Spring Bean的生命周期

要理解循环依赖的解决机制,首先需要了解Spring Bean的创建过程。Spring Bean的生命周期主要包括以下步骤: 1. 实例化(Instantiation):通过反射调用构造函数创建对象。 2. 属性填充(Population):注入依赖的Bean(通过@Autowired@Resource等)。 3. 初始化(Initialization):执行InitializingBean#afterPropertiesSet()@PostConstruct方法。 4. 销毁(Destruction):容器关闭时执行销毁逻辑。

2.2 三级缓存机制

Spring通过三级缓存解决循环依赖问题。三级缓存分别是: 1. singletonObjects(一级缓存):存储完全初始化好的Bean。 2. earlySingletonObjects(二级缓存):存储提前暴露的原始Bean(尚未填充属性)。 3. singletonFactories(三级缓存):存储Bean的工厂对象(ObjectFactory),用于生成原始Bean的代理对象(如AOP场景)。

2.3 解决循环依赖的流程

以下以ServiceAServiceB的循环依赖为例,说明Spring的处理流程: 1. 创建ServiceA: - 实例化ServiceA,此时ServiceA是一个原始对象(未填充属性)。 - 将ServiceA的工厂对象放入三级缓存(singletonFactories)。 - 开始填充ServiceA的属性,发现需要注入ServiceB

  1. 创建ServiceB

    • 实例化ServiceB,此时ServiceB是一个原始对象。
    • ServiceB的工厂对象放入三级缓存。
    • 填充ServiceB的属性,发现需要注入ServiceA
  2. 解决ServiceA的依赖

    • 从三级缓存中获取ServiceA的工厂对象,生成ServiceA的早期引用(可能是代理对象)。
    • ServiceA的早期引用放入二级缓存,并删除三级缓存中的工厂对象。
    • ServiceA的早期引用注入到ServiceB中。
    • ServiceB完成属性填充和初始化,放入一级缓存。
  3. 完成ServiceA的创建

    • ServiceB的实例注入到ServiceA中。
    • ServiceA完成初始化,放入一级缓存。

2.4 关键源码分析

Spring的核心逻辑在DefaultSingletonBeanRegistryAbstractAutowireCapableBeanFactory中。以下是关键代码片段:

// DefaultSingletonBeanRegistry.java
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

三、循环依赖的局限性

3.1 构造器循环依赖无法解决

如果循环依赖是通过构造函数注入实现的,Spring会直接抛出BeanCurrentlyInCreationException异常。例如:

@Service
public class ServiceA {
    private final ServiceB serviceB;
    public ServiceA(ServiceB serviceB) { this.serviceB = serviceB; }
}

@Service
public class ServiceB {
    private final ServiceA serviceA;
    public ServiceB(ServiceA serviceA) { this.serviceA = serviceA; }
}

原因:构造器注入需要完全实例化Bean,无法通过提前暴露引用解决。

3.2 原型作用域的Bean不支持循环依赖

Spring不会缓存prototype作用域的Bean,因此无法通过三级缓存解决循环依赖。

3.3 AOP代理的循环依赖

如果循环依赖的Bean需要被AOP代理(如@Transactional),Spring会通过SmartInstantiationAwareBeanPostProcessor提前生成代理对象。此时三级缓存中的工厂对象会返回代理对象而非原始对象。


四、如何避免循环依赖

4.1 设计层面

4.2 技术层面

@Service
public class ServiceA {
    @Autowired
    @Lazy
    private ServiceB serviceB;
}

五、总结

Spring通过三级缓存机制(singletonFactoriesearlySingletonObjectssingletonObjects)解决了属性注入的循环依赖问题,但其核心思想是提前暴露原始对象的引用。然而,构造器循环依赖和原型Bean的循环依赖无法通过此机制解决。在实际开发中,应尽量避免循环依赖,以保持代码的清晰性和可维护性。


附录:常见问题解答

Q1:为什么构造器循环依赖无法解决?

A1:构造器注入需要在实例化阶段完成所有依赖注入,而三级缓存机制依赖提前暴露引用,两者冲突。

Q2:Spring为什么选择三级缓存而不是二级缓存?

A2:三级缓存中的ObjectFactory可以处理AOP代理等复杂场景,确保返回的对象是最终需要的代理对象。

Q3:如何检测项目中的循环依赖?

A3:可以通过Spring启动日志(BeanCurrentlyInCreationException)或工具(如ArchUnit)检测。


参考文档: - Spring Framework官方文档 - 《Spring源码深度解析》 - DefaultSingletonBeanRegistry源码 “`

(注:实际字数约2800字,可通过扩展示例、源码分析或案例分析补充至3300字。)

推荐阅读:
  1. Spring怎么解决循环依赖的问题
  2. Spring循环依赖的方式有哪些

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

spring

上一篇:HTML5中如何实现网页录音和上传到服务器功能

下一篇:html5中input元素新特性有哪些

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》