Spring 解决构造方法注入的循环依赖了吗?解决多例下的循环依赖了吗?

发布于:2025-06-25 ⋅ 阅读:(18) ⋅ 点赞:(0)

程序员面试资料大全|各种技术书籍等资料-1000G


一、构造方法注入的循环依赖:❌ 完全不支持

问题本质
需要Bean B
需要Bean A
创建Bean A
创建Bean B
  • 死锁场景:对象未实例化前就必须完成构造器调用,而构造参数依赖的对象同样未被创建
  • Spring 的设计约束:必须在对象实例化(调用构造器)后才能暴露早期引用(三级缓存机制的前提)
源码验证(AbstractAutowireCapableBeanFactory
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
    // 构造器注入会直接尝试获取依赖Bean
    Constructor<?> constructorToUse = determineConstructor(beanName, mbd);
    // 若依赖的Bean正在创建中(即循环依赖),立即抛出异常
    if (mbd.isPrototype() && mbd.getDependentBeans().contains(beanName)) {
        throw new BeanCurrentlyInCreationException(beanName);
    }
    return instantiateBean(beanName, mbd); // 此处触发异常
}

异常信息示例

Error creating bean with name 'beanA': 
Requested bean is currently in creation: Is there an unresolvable circular reference?
规避方案
  1. 改用 Setter/Field 注入
    @Component
    public class BeanA {
        private BeanB beanB;
        
        @Autowired  // 改为属性注入
        public void setBeanB(BeanB beanB) {
            this.beanB = beanB;
        }
    }
    
  2. 延迟注入(@Lazy
    @Component
    public class BeanA {
        private final BeanB beanB;
        
        public BeanA(@Lazy BeanB beanB) { // 延迟代理
            this.beanB = beanB;
        }
    }
    
    • 原理:创建代理对象而非真实 Bean,打破实例化死锁
    • 风险:可能将异常延迟到运行时

二、多例(Prototype)作用域的循环依赖:❌ 完全不支持

核心原因
每次getBean
注入时需获取Bean B
注入时需获取Bean A
Prototype Bean A
创建新实例
Prototype Bean B
再次创建Bean A...
  • 无缓存机制:Prototype Bean 每次 getBean() 都生成新对象
  • 三级缓存失效singletonObjects/earlySingletonObjects/singletonFactories 仅缓存单例 Bean
源码逻辑(AbstractBeanFactory
if (mbd.isPrototype()) {
    if (isPrototypeCurrentlyInCreation(beanName)) {
        // 发现当前线程已在创建该Prototype Bean → 循环依赖
        throw new BeanCurrentlyInCreationException(beanName);
    }
    beforePrototypeCreation(beanName); // 标记创建状态
    prototypeInstance = createBean(beanName, mbd, args);
}

关键限制

  • prototypesCurrentlyInCreation 线程变量仅用于检测循环依赖,而非解决
典型报错
Error creating bean with name 'beanA': 
Scope 'prototype' is not active for the current thread; 
Consider defining a scoped proxy for this bean if you intend to refer to it from a singleton;

三、对比总结:Spring 解决循环依赖的能力边界

依赖类型 是否支持解决 原因
单例 + Setter/Field注入 三级缓存可提前暴露半成品对象
单例 + 构造器注入 对象未实例化无法暴露引用
多例(任何注入方式) 无对象缓存机制,每次创建都是独立流程

四、终极解决方案

1. 代码重构(治本之策)
  • 引入中间层
    @Component
    public class ServiceBridge {
        @Autowired private BeanA beanA;
        @Autowired private BeanB beanB;
    }
    
  • 事件驱动解耦
    // BeanA 发布事件
    applicationContext.publishEvent(new EventA());
    
    // BeanB 监听事件
    @EventListener
    public void handleEvent(EventA event) {...}
    
2. 使用 Provider 延迟获取(推荐)
@Component
public class BeanA {
    private final Provider<BeanB> beanBProvider; // javax.inject.Provider

    public BeanA(Provider<BeanB> beanBProvider) {
        this.beanBProvider = beanBProvider;
    }

    public void execute() {
        BeanB beanB = beanBProvider.get(); // 使用时才创建
    }
}
3. 方法注入(仅适用原型)
@Component
public abstract class BeanA {
    public void process() {
        BeanB beanB = createBeanB();
    }
    
    @Lookup // Spring会动态生成子类实现此方法
    protected abstract BeanB createBeanB();
}

五、设计启示

  1. 避免循环依赖是首要原则

    • 70% 可通过调整代码结构避免
    • 20% 通过延迟加载解决
    • 10% 考虑架构重构
  2. 慎用构造器注入场景

    • 强依赖关系(如数据库连接池)→ 适合构造器注入
    • 弱依赖关系(如工具类)→ 优先Setter注入
  3. 原型Bean使用规范

    • 永远不注入单例Bean(会导致状态污染)
    • ObjectFactoryProvider 封装获取逻辑

程序员面试资料大全|各种技术书籍等资料-1000G

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到