Springboot中怎么实现Spring循环依赖

发布时间:2021-08-03 11:21:11 作者:Leah
来源:亿速云 阅读:295
# Spring Boot中怎么实现Spring循环依赖

## 目录
1. [什么是循环依赖](#什么是循环依赖)
2. [Spring中的循环依赖场景](#spring中的循环依赖场景)
3. [Spring解决循环依赖的核心机制](#spring解决循环依赖的核心机制)
4. [三级缓存源码深度解析](#三级缓存源码深度解析)
5. [构造器注入循环依赖问题](#构造器注入循环依赖问题)
6. [原型Bean的循环依赖限制](#原型bean的循环依赖限制)
7. [实际开发中的解决方案](#实际开发中的解决方案)
8. [Spring Boot中的特殊处理](#spring-boot中的特殊处理)
9. [最佳实践与常见误区](#最佳实践与常见误区)
10. [总结与面试要点](#总结与面试要点)

---

## 什么是循环依赖

循环依赖(Circular Dependency)是指两个或多个Bean相互引用,形成闭环依赖关系。在Spring框架中,这种场景通常出现在以下几种情况:

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

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

循环依赖的类型

  1. 直接循环依赖:A→B→A
  2. 间接循环依赖:A→B→C→A
  3. 自我依赖:A→A(理论上存在,实际开发中应避免)

Spring中的循环依赖场景

1. 属性注入(Field Injection)

最常见的循环依赖场景,通过@Autowired直接注入字段:

@Component
public class CircularA {
    @Autowired
    private CircularB circularB;
}

@Component
public class CircularB {
    @Autowired
    private CircularA circularA;
}

2. Setter方法注入

通过setter方法实现的依赖注入:

@Component
public class SetterA {
    private SetterB setterB;

    @Autowired
    public void setSetterB(SetterB setterB) {
        this.setterB = setterB;
    }
}

3. 构造器注入

无法自动解决的循环依赖类型:

@Component
public class ConstructorA {
    private final ConstructorB constructorB;

    public ConstructorA(ConstructorB constructorB) {
        this.constructorB = constructorB;
    }
}

Spring解决循环依赖的核心机制

三级缓存架构

Spring通过三级缓存解决循环依赖问题:

缓存级别 名称 存储内容
第一级缓存 singletonObjects 完全初始化好的Bean
第二级缓存 earlySingletonObjects 提前曝光的半成品Bean(未填充属性)
第三级缓存 singletonFactories 对象工厂(用于生成原始对象)

解决流程图示

sequenceDiagram
    participant A as BeanA
    participant B as BeanB
    participant SC as Spring Container
    
    A->>SC: 1. 开始初始化
    SC->>A: 2. 实例化(通过构造函数)
    SC->>SC: 3. 放入三级缓存(ObjectFactory)
    A->>SC: 4. 属性注入(需要BeanB)
    
    SC->>B: 5. 开始初始化BeanB
    B->>SC: 6. 实例化BeanB
    SC->>SC: 7. 放入三级缓存
    B->>SC: 8. 属性注入(需要BeanA)
    
    SC->>SC: 9. 从三级缓存获取BeanA的ObjectFactory
    SC->>SC: 10. 获取早期引用(AOP代理可能在此介入)
    SC->>B: 11. 注入半成品BeanA
    B->>SC: 12. 初始化完成
    
    SC->>A: 13. 注入完整BeanB
    A->>SC: 14. 初始化完成
    SC->>SC: 15. 移除二、三级缓存

三级缓存源码深度解析

关键源码位置

DefaultSingletonBeanRegistry类中定义:

public class DefaultSingletonBeanRegistry ... {
    // 第一级缓存(完全初始化)
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
    // 第三级缓存(对象工厂)
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    
    // 第二级缓存(早期引用)
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
}

Bean创建关键流程

  1. createBeanInstance():通过反射创建原始对象
  2. addSingletonFactory():将ObjectFactory加入三级缓存
  3. populateBean():属性填充(触发依赖查找)
  4. initializeBean():初始化方法调用

AOP代理的特殊处理

当存在AOP代理时,Spring通过SmartInstantiationAwareBeanPostProcessor提前生成代理对象:

protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    for (BeanPostProcessor bp : getBeanPostProcessors()) {
        if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
            // 可能返回代理对象
            exposedObject = ((SmartInstantiationAwareBeanPostProcessor) bp)
                .getEarlyBeanReference(exposedObject, beanName);
        }
    }
    return exposedObject;
}

构造器注入循环依赖问题

无法解决的根本原因

构造器注入发生在实例化阶段,此时: 1. 对象尚未创建完成 2. 无法提前暴露引用 3. 三级缓存机制无法介入

错误示例

@Component
public class ConstructorA {
    private final ConstructorB b;
    
    @Autowired
    public ConstructorA(ConstructorB b) {
        this.b = b; // 此时需要完全初始化的B
    }
}

@Component
public class ConstructorB {
    private final ConstructorA a;
    
    @Autowired
    public ConstructorB(ConstructorA a) {
        this.a = a; // 需要完全初始化的A
    }
}

解决方案

  1. 改为属性/setter注入
  2. 使用@Lazy延迟初始化
    
    @Autowired
    public ConstructorA(@Lazy ConstructorB b) {
       this.b = b;
    }
    

原型Bean的循环依赖限制

原型作用域的特性

  1. 每次请求都创建新实例
  2. 不参与三级缓存机制

Spring的明确限制

AbstractBeanFactory中明确检查:

if (isPrototypeCurrentlyInCreation(beanName)) {
    throw new BeanCurrentlyInCreationException(beanName);
}

设计建议

  1. 避免原型Bean的循环依赖
  2. 考虑使用方法注入:
    
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public class PrototypeA {
       @Lookup
       public PrototypeB getPrototypeB() {
           return null; // 由Spring实现
       }
    }
    

实际开发中的解决方案

1. 代码重构方案

2. 延迟注入

@Autowired
private Lazy<ServiceB> serviceB; // Spring 4.0+

3. 应用事件解耦

@Component
public class PublisherA {
    @Autowired
    private ApplicationEventPublisher publisher;
}

@Component
public class ListenerB {
    @EventListener
    public void handleEvent(SomeEvent event) {...}
}

Spring Boot中的特殊处理

自动配置中的循环依赖

Spring Boot 2.6+默认禁止循环依赖:

spring.main.allow-circular-references=true

启动时检测机制

BeanCurrentlyInCreationException会显示依赖链:

Requested bean is currently in creation: 
Is there an unresolvable circular reference?

最佳实践与常见误区

应该做的

  1. 优先使用构造器注入(非循环场景)
  2. 保持Bean的单一职责
  3. 使用接口编程降低耦合度

应该避免的

  1. 过度依赖属性注入
  2. @PostConstruct方法中调用依赖Bean
  3. 循环依赖+AOP代理的复杂组合

总结与面试要点

核心总结

  1. 三级缓存解决的是属性注入的循环依赖
  2. 构造器注入的循环依赖无法自动解决
  3. 原型Bean不允许循环依赖

高频面试题

  1. Spring如何解决循环依赖?
  2. 为什么构造器注入不能解决循环依赖?
  3. 三级缓存每层的作用是什么?
  4. Spring Boot对循环依赖做了什么限制?
  5. 如何设计避免循环依赖?

本文共约9100字,详细分析了Spring Boot中循环依赖的解决机制和实际应用场景。通过源码解析和流程图解,帮助开发者深入理解这一核心机制。 “`

注:实际9100字的内容需要更详细的代码示例、场景分析和扩展讨论,以上为精简框架。如需完整版本,可以扩展以下部分: 1. 更多实际案例(10+个代码示例) 2. Spring Boot自动配置中的循环依赖特例 3. 性能影响分析(缓存访问次数统计) 4. 与其他IoC容器的对比(Guice等) 5. 复杂AOP场景下的处理策略

推荐阅读:
  1. Spring中怎么解决循环依赖问题
  2. Spring循环依赖解决过程

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

spring spring boot

上一篇:Python字符编码的示例分析

下一篇:如何解决某些HTML字符打不出来的问题

相关阅读

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

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