You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring PROTOTYPE作用域Bean循环依赖问题及解决方案咨询

Spring原型Bean循环依赖问题解答

你遇到的这个问题是Spring的一个明确限制:Spring原生不支持解决原型(PROTOTYPE)作用域Bean的循环依赖,这也是为什么你的单例Bean能正常运行,但改成原型后就抛出循环依赖异常的原因。你看到的错误提示也印证了这一点:

描述:
应用上下文部分Bean的依赖形成循环:
demoApplication (field private com.example.demo.beans.BeanA com.example.demo.DemoApplication.beanA)

为什么单例可以,原型不行?

这里的核心差异在于Spring对不同作用域Bean的缓存机制:

  • 单例Bean:Spring通过三级缓存(singletonObjects、earlySingletonObjects、singletonFactories)实现循环依赖解决——在Bean还未完全初始化时,就提前暴露它的代理对象或原始引用,让依赖它的Bean先拿到实例,后续再完成初始化流程。
  • 原型Bean:每次请求都会创建全新的实例,Spring不会将原型Bean存入任何缓存。当创建BeanA的原型实例时,需要注入BeanB的原型实例;创建BeanB的原型实例时,又需要注入BeanA的原型实例,直接陷入无限循环的创建链条,最终触发循环依赖异常。

可行的解决方案

1. 使用@Lazy延迟初始化

在其中一个Bean的依赖注入处添加@Lazy注解,让依赖的Bean在真正被使用时才创建,而不是在当前Bean初始化时就触发创建,从而打破循环链条:

@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class BeanA {
    private BeanB beanB;

    @Autowired
    public void setBeanB(@Lazy BeanB beanB) {
        this.beanB = beanB;
    }

    // 其他方法保持不变
}

此时创建BeanA的原型实例时,注入的是一个代理对象,只有当你调用beanB的方法时,Spring才会去创建BeanB的原型实例,而这时BeanA已经完成初始化,不会触发循环。

2. 使用ObjectProvider延迟获取实例

通过Spring的ObjectProvider(或JSR-330的Provider)来封装依赖,每次调用getObject()才会生成新的原型Bean实例,避免初始化时的循环创建:

import org.springframework.beans.factory.ObjectProvider;

@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class BeanA {
    private ObjectProvider<BeanB> beanBProvider;

    @Autowired
    public void setBeanBProvider(ObjectProvider<BeanB> beanBProvider) {
        this.beanBProvider = beanBProvider;
    }

    public void printBeanABeanB() {
        // 真正使用时才获取BeanB的原型实例
        BeanB beanB = beanBProvider.getObject();
        System.out.println(getBeanName() + " - " + beanB.getBeanName());
    }

    // 其他方法保持不变
}

这种方式既解决了循环依赖,又完美保留了原型Bean每次获取新实例的特性。

3. 通过ApplicationContext手动获取Bean

在Bean中注入ApplicationContext,在需要使用依赖Bean时手动调用getBean()方法获取实例,同样是延迟创建来避免循环:

import org.springframework.context.ApplicationContext;

@Service
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class BeanA {
    private ApplicationContext applicationContext;

    @Autowired
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    public void printBeanABeanB() {
        BeanB beanB = applicationContext.getBean(BeanB.class);
        System.out.println(getBeanName() + " - " + beanB.getBeanName());
    }

    // 其他方法保持不变
}

注意:这种方式会增加代码对Spring上下文的耦合度,尽量只在临时场景使用。

4. 重构设计,从根源打破循环依赖

最优雅的长期解决方案是重构代码结构,移除循环依赖:

  • 提取两个Bean的公共逻辑到一个新的独立Bean(比如CommonService),让BeanA和BeanB都依赖这个新Bean,而非互相依赖。
  • 调整交互方式,比如通过事件驱动、回调接口等方式,让两个Bean不再直接持有对方的引用。

总结

Spring原生不支持原型Bean的循环依赖,但可以通过延迟初始化、Provider封装、手动获取实例等方式绕过这个限制;如果项目允许,重构设计打破循环依赖是最推荐的方案,能让代码结构更清晰、耦合度更低。

内容的提问来源于stack exchange,提问作者Chamly Idunil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 09:02:02