Spring Boot中原型Bean注入单例时不会每次创建新实例,因@Autowired是一次性注入;需改用ObjectProvider、@Lookup、ApplicationContext.getBean或代理方式动态获取。

Spring Boot 中原型 Bean 注入单例时,**不会按预期每次创建新实例**——单例 Bean 初始化时只注入一次原型 Bean,之后始终复用该实例。这不是配置错误,而是 Spring 依赖注入机制的固有行为。关键在于:原型作用域生效的前提是“每次获取”,而@Autowired 是“一次性注入”。要真正实现每次调用都拿到新对象,必须绕过静态注入,改用动态获取方式。
为什么 @Scope("prototype") 单独加没用
单例 Bean 在容器启动时完成实例化和属性注入,此时 Spring 调用一次 getBean(PrototypeBean.class) 创建并注入一个实例,之后整个生命周期内都使用这个对象。即使原型 Bean 类上标注了 @Scope("prototype"),也不会改变已注入引用的指向。结果就是:看似是原型,实则被“固化”成了单例引用。
四种可靠解决方式
核心思路都是让单例 Bean **在每次需要时才主动获取**原型实例,而不是在初始化时就持有固定引用:
- @Lookup 方法注入:在单例类中定义一个抽象方法,加上 @Lookup 注解,Spring 会动态生成子类并重写该方法,每次调用都返回新原型实例。适用于类可继承、方法签名固定的场景。
-
ObjectProvider<T>:将 @Autowired 的字段类型改为 ObjectProvider
,然后在业务方法中调用 getObject() 获取新实例。轻量、无侵入、推荐首选。 - ApplicationContext 手动获取:在单例 Bean 中注入 ApplicationContext,需要时调用 applicationContext.getBean(PrototypeBean.class)。灵活但耦合容器 API,适合临时或特殊逻辑。
- @Lazy + 代理(慎用):配合 @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS) 可生成 CGLIB 代理,拦截方法调用并每次新建目标对象。但仅适用于原型 Bean 有接口且需透明替换的少数情况,易引发 AOP 冲突,不建议常规使用。
怎么选?看实际需求
如果只是偶尔需要新实例,用 ObjectProvider 最干净;如果原型 Bean 是工具类且调用频繁,@Lookup 更符合面向对象设计;若已有 ApplicationContext 引用或需跨上下文获取,手动 getBean 也完全可行。避免只加 @Scope 而不做获取方式调整,否则内存泄漏和状态污染风险真实存在。


















