Spring循环依赖详解

Spring 循环依赖详解

一、循环依赖

1. 什么是循环依赖

循环依赖是指两个或多个Spring Bean之间形成相互依赖的闭环关系。具体表现为:
Bean A 依赖 Bean B
Bean B 依赖 Bean C

Bean N 又依赖 Bean A

2. 循环依赖出现的场景

循环依赖通常出现在以下场景中:

  • Bean初始化阶段
  • 当Spring容器启动,初始化Bean时
  • 特别是使用构造器注入方式时最容易暴露
  • 依赖注入时
  • 通过@Autowired进行字段/方法注入时
  • 使用XML配置中的或时
  • 特定设计模式中
  • 双向关联的业务场景(如订单-支付系统)
  • 相互回调的组件设计

3. 循环依赖会带来什么问题

  • 启动时抛出BeanCurrentlyInCreationException
  • 错误信息:“Requested bean is currently in creation: Is there an unresolvable circular reference?”
  • 每次获取bean都会尝试创建新实例
  • 最终导致栈溢出(StackOverflowError)

二、循环依赖的几种类型

1. Setter/Field注入循环依赖

@Component
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
}

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

2. 构造器循环依赖

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

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

三、Spring解决循环依赖的机制

1. 三级缓存机制(可解决注入循环依赖)

1.1 三级缓存

在初始化阶段(实例化之后),Spring使用三级缓存来解决单例Bean的循环依赖问题:

缓存级别名称存储内容作用
一级缓存singletonObjects完全初始化好的Bean提供最终可用的Bean
二级缓存earlySingletonObjects提前曝光的半成品Bean解决循环依赖
三级缓存singletonFactories对象工厂(ObjectFactory)生成代理对象

这是Spring三级缓存的部分源码

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
	/** Cache of singleton objects: bean name to bean instance. */
	//  一级缓存
	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

	/** Cache of singleton factories: bean name to ObjectFactory. */
	//  三级缓存
	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

	/** Cache of early singleton objects: bean name to bean instance. */
	//  二级缓存
	private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
	...
}
1.2 解决流程(以A→B→A为例)
  1. 开始创建A,实例化A(调用构造器)
  2. 将A的对象工厂放入三级缓存
  3. 填充A的属性,发现需要B
  4. 开始创建B,实例化B(调用构造器)
  5. 将B的对象工厂放入三级缓存
  6. 填充B的属性,发现需要A
  7. 从三级缓存获取A的对象工厂,生成早期引用
  8. 将A的早期引用放入二级缓存,删除三级缓存中的A
  9. B完成属性注入,初始化完成
  10. A得到B的引用,继续完成初始化
  11. A初始化完成,放入一级缓存
  12. B中的A引用最终指向完全初始化的A

在这里插入图片描述

1.3 关键源码分析

DefaultSingletonBeanRegistry类中的关键方法:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1. 从一级缓存查询
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 2. 从二级缓存查询
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 3. 从三级缓存获取ObjectFactory
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

2. 懒加载(可解决构造循环依赖)

在实例化阶段,可使用@Lazy注解通过以下方式解决构造器循环依赖:
不立即初始化依赖的Bean
创建一个代理对象作为占位符
当实际需要调用依赖Bean时才进行初始化

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

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

四、如何避免/解决循环依赖

1. 设计层面解决方案

  1. 重构代码:解耦相互依赖的组件
  2. 接口抽象:依赖接口而非具体实现
  3. 应用事件:使用事件机制代替直接调用
  4. 模板方法模式:将公共逻辑提取到父类

2. 技术层面解决方案

  1. 使用@Lazy延迟加载
@Component
public class ServiceA {
    @Lazy
    @Autowired
    private ServiceB serviceB;
}
  1. 使用Setter注入代替构造器注入
@Component
public class ServiceA {
    private ServiceB serviceB;
    @Autowired
    public void setServiceB(ServiceB serviceB) { this.serviceB = serviceB; }
}
  1. 使用@DependsOn指定初始化顺序
@Component
@DependsOn("serviceB")
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
}
  1. 使用ApplicationContextAware手动获取Bean
@Component
public class ServiceA implements ApplicationContextAware {
    private ApplicationContext context;
    
    @Override
    public void setApplicationContext(ApplicationContext context) {
        this.context = context;
    }
    
    public void doSomething() {
        ServiceB serviceB = context.getBean(ServiceB.class);
        // 使用serviceB
    }
}

五、特殊场景处理

1. AOP代理下的循环依赖

Spring通过SmartInstantiationAwareBeanPostProcessor处理代理对象的循环依赖,关键类是AbstractAutoProxyCreator

2. 构造器注入的替代方案

如果必须使用构造器注入,可以考虑:

@Configuration
public class MyConfig {
    @Bean
    public ServiceA serviceA(@Lazy ServiceB serviceB) {
        return new ServiceA(serviceB);
    }
}

六、最佳实践建议

  1. 尽量避免循环依赖,这是代码设计问题而非技术问题
  2. 优先使用构造器注入(Spring官方推荐)
  3. 对于不可避免的循环依赖,使用Setter/Field注入
  4. 在大型项目中,使用模块化设计减少循环依赖
  5. 定期使用架构分析工具检测循环依赖
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值