Spring 循环依赖问题

什么是循环依赖

循环依赖是指两个或多个 Bean 相互依赖,形成闭环:

┌─────────┐     ┌─────────┐
│  Bean A │────▶│  Bean B │
└────┬────┘     └────┬────┘
     │               │
     └───────┬───────┘
             ▼
      ┌─────────┐
      │  Bean C │
      └────┬────┘
           │
           └──────┐
                  ▼
            ┌─────────┐
            │  Bean A │  ← 形成闭环
            └─────────┘

Spring 解决循环依赖的机制

核心原理:三级缓存

Spring 使用三级缓存来解决单例 Bean 的循环依赖问题:

/** 一级缓存:存放完全初始化的 Bean */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** 二级缓存:存放提前暴露的 Bean(半成品) */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

/** 三级缓存:存放 Bean 工厂,用于生成提前暴露的 Bean */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

三级缓存工作流程

创建 Bean A
    │
    ▼
实例化 A(调用构造器)
    │
    ▼
暴露半成品 A 到三级缓存(singletonFactories)
    │
    ▼
填充属性 A → 需要 Bean B
    │
    ▼
创建 Bean B
    │
    ▼
实例化 B
    │
    ▼
暴露半成品 B 到三级缓存
    │
    ▼
填充属性 B → 需要 Bean A
    │
    ▼
从三级缓存获取 A → 移到二级缓存(earlySingletonObjects)
    │
    ▼
B 初始化完成 → 移到一级缓存(singletonObjects)
    │
    ▼
A 获取到 B,继续初始化
    │
    ▼
A 初始化完成 → 移到一级缓存
    │
    ▼
循环依赖解决!

代码示例

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

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

Spring 处理过程:

// 伪代码:Spring 创建 Bean 的核心逻辑
protected Object getSingleton(String beanName) {
    // 1. 从一级缓存获取(完全初始化的 Bean)
    Object singletonObject = singletonObjects.get(beanName);
    if (singletonObject == null) {
        // 2. 从二级缓存获取(提前暴露的 Bean)
        singletonObject = earlySingletonObjects.get(beanName);
        if (singletonObject == null) {
            // 3. 从三级缓存获取(Bean 工厂)
            ObjectFactory<?> singletonFactory = singletonFactories.get(beanName);
            if (singletonFactory != null) {
                // 调用工厂方法获取 Bean(可能返回代理对象)
                singletonObject = singletonFactory.getObject();
                // 移到二级缓存
                earlySingletonObjects.put(beanName, singletonObject);
                singletonFactories.remove(beanName);
            }
        }
    }
    return singletonObject;
}

构造器注入可以循环依赖吗?

❌ 不可以!

构造器注入无法解决循环依赖,会抛出异常:

BeanCurrentlyInCreationException: Error creating bean with name 'serviceA': 
Requested bean is currently in creation: Is there an unresolvable circular reference?

原因分析

创建 Bean A
    │
    ▼
调用构造器 A(ServiceB) → 需要 Bean B
    │
    ▼
创建 Bean B
    │
    ▼
调用构造器 B(ServiceA) → 需要 Bean A
    │
    ▼
A 还在创建中,无法提供实例 → 💥 异常!

构造器注入要求所有依赖在实例化时就绪,而此时 A 还未完成实例化,无法提供给 B。


解决方案

1. 使用 Setter 注入或字段注入 ✅

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

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

2. 使用 @Lazy 注解 ✅

@Service
public class ServiceA {
    private final ServiceB serviceB;
    
    @Autowired
    public ServiceA(@Lazy ServiceB serviceB) {
        this.serviceB = serviceB; // 注入代理对象,真正使用时才创建
    }
}

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

3. 使用 @PostConstruct 初始化 ✅

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
    
    @PostConstruct
    public void init() {
        // 在这里使用 serviceB
    }
}

4. 重构设计,消除循环依赖 ✅(推荐)

// 提取公共接口
public interface CommonService {
    void doSomething();
}

@Service
public class ServiceA implements CommonService {
    private final CommonService commonService;
    
    @Autowired
    public ServiceA(CommonService commonService) {
        this.commonService = commonService;
    }
}

@Service
public class ServiceB implements CommonService {
    private final CommonService commonService;
    
    @Autowired
    public ServiceB(CommonService commonService) {
        this.commonService = commonService;
    }
}

循环依赖解决能力对比

注入方式 能否解决循环依赖 原因
构造器注入 ❌ 不能 实例化时就需要依赖对象
Setter 注入 ✅ 能 实例化后再注入依赖
字段注入 ✅ 能 实例化后再注入依赖
@Lazy ✅ 能 延迟加载,注入代理对象

注意事项

⚠️ 原型 Bean 不支持循环依赖

@Scope("prototype")
@Service
public class PrototypeA {
    @Autowired
    private PrototypeB prototypeB;
}

@Scope("prototype")
@Service
public class PrototypeB {
    @Autowired
    private PrototypeA prototypeA;
}

原型 Bean 每次都创建新实例,Spring 不会缓存,无法解决循环依赖。

⚠️ @Async 导致的循环依赖

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
    
    @Async
    public void asyncMethod() {
        serviceB.doSomething();
    }
}

@Async 会生成代理对象,可能导致循环依赖问题,需要使用 @Lazy 或重构。


总结

问题 答案
Spring 如何解决循环依赖? 三级缓存机制
构造器注入可以循环依赖吗? ❌ 不可以
Setter/字段注入可以吗? ✅ 可以
最佳解决方案? 重构设计,消除循环依赖

推荐做法:优先使用构造器注入,遇到循环依赖时使用 @Lazy 或重构设计。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐