解决 Spring 单例 Bean 注入原型 Bean 被共享的问题

在使用 Spring 开发应用时,Bean 的作用域(Scope) 决定了 Bean 实例的生命周期。Spring 提供了多种作用域,其中最常见的是 单例(singleton)原型(prototype) 作用域。

  • 单例作用域(singleton):容器中只有一个实例,整个应用上下文中的 Bean 共享同一个实例。
  • 原型作用域(prototype):每次请求都会创建一个新的实例,每个请求返回一个不同的对象。

理论上,原型 Bean 的每次请求都应返回一个新的实例,而单例 Bean 应该只存在一个实例。问题发生在当 原型 Bean 注入到单例 Bean 中时,原型 Bean 的实例化行为可能并不像预期的那样,每次都返回新的实例。

一、问题描述

假设我们有一个 单例 Bean SingletonBean,它需要使用一个 原型 Bean PrototypeBean 来完成某些功能。通常,我们期望每次调用 SingletonBean 中的 PrototypeBean 时,都能获得一个新的实例。然而,由于 Spring 的默认行为,PrototypeBeanSingletonBean 中可能会被共享,这与我们希望原型 Bean 每次创建新实例的设计相冲突。

示例代码
@Component
@Scope("singleton")  // SingletonBean 是单例作用域
public class SingletonBean {@Autowiredprivate PrototypeBean prototypeBean; // 注入原型 Beanpublic void doSomething() {prototypeBean.doSomething();  // 每次都会调用同一个实例}
}@Component
@Scope("prototype")  // PrototypeBean 是原型作用域
public class PrototypeBean {public void doSomething() {System.out.println("PrototypeBean doing something");}
}

在这个示例中:

  • SingletonBean 是单例作用域的,每次访问都会返回相同的实例。
  • PrototypeBean 是原型作用域的,本应每次请求时创建新的实例,但由于它被注入到 SingletonBean 中,Spring 只会在容器启动时实例化一次 PrototypeBean,并将该实例注入到 SingletonBean 中。

问题的本质是:尽管 PrototypeBean 是原型作用域的,Spring 会在 SingletonBean 创建时注入它的实例,并且在 SingletonBean 的整个生命周期内使用同一个 PrototypeBean 实例,而不会每次都创建新的实例。

二、问题产生的原因

问题的根源在于 Spring 的依赖注入机制

  1. Spring 容器在启动时创建所有的 Bean。对于单例作用域的 Bean,Spring 会在容器初始化时创建实例,并且在整个应用生命周期内保持该实例。而原型作用域的 Bean 每次被请求时都会创建新实例。

  2. 单例 Bean 中注入原型 Bean 时,Spring 会在容器初始化时就实例化 PrototypeBean,并将其注入到 SingletonBean 中。即使 PrototypeBean 是原型作用域的,它的实例也会在容器初始化时就被创建,并且这个实例会在 SingletonBean 的整个生命周期内被共享。

  3. 没有延迟加载机制:单例 Bean 中的 PrototypeBean 注入后,Spring 并不会每次重新创建一个新的实例,而是使用已经注入的原型 Bean 实例。因此,每次访问 SingletonBean 时,得到的 PrototypeBean 实例是同一个,而不是一个新的实例。

三、解决方案

为了确保 单例 Bean 中注入的原型 Bean 每次都能返回新的实例,可以通过以下几种方法来解决这个问题。

1. 使用 ObjectProvider

ObjectProvider 是 Spring 提供的一种 延迟加载机制。通过 ObjectProvider,我们可以在需要时动态地获取原型 Bean,确保每次调用时都返回一个新的实例。

@Component
public class SingletonBean {@Autowiredprivate ObjectProvider<PrototypeBean> prototypeBeanProvider;public void doSomething() {// 每次调用 getObject() 时都会获取新的 PrototypeBean 实例PrototypeBean prototypeBean = prototypeBeanProvider.getObject();prototypeBean.doSomething();}
}
  • ObjectProvider<PrototypeBean>PrototypeBean 以延迟加载的方式实例化,每次调用 getObject() 时都会返回新的实例。
  • 这种方法可以确保每次调用时都获得一个新的原型 Bean 实例,而不是复用已注入的实例。
2. 使用 ApplicationContext 获取原型 Bean

ApplicationContext 提供了一个方法 getBean(),可以动态获取原型作用域的 Bean。通过这种方式,我们可以确保每次获取原型 Bean 时,都会得到一个新的实例。

@Component
public class SingletonBean {@Autowiredprivate ApplicationContext applicationContext;public void doSomething() {// 每次调用 getBean() 都会获取新的 PrototypeBean 实例PrototypeBean prototypeBean = applicationContext.getBean(PrototypeBean.class);prototypeBean.doSomething();}
}
  • applicationContext.getBean(PrototypeBean.class) 每次调用时都会返回新的 PrototypeBean 实例,而不是使用已经注入的实例。
  • 这种方法适用于需要通过 ApplicationContext 动态获取原型 Bean 的场景。
3. 结合 @Scope("prototype")@Lazy 注解

@Lazy 注解可以延迟 Bean 的初始化,确保原型 Bean 只在第一次需要时才会被创建。这结合 @Scope("prototype") 注解可以确保每次请求时都会创建新的实例。

package com.nbsaas.boot.controller.web;import com.nbsaas.boot.rest.response.ResponseObject;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class StateController {private Integer account=0;@Resourceprivate  ObjectProvider<StateHandle> stateHandles;@Lazy@Resourceprivate StateHandle stateHandle;@RequestMapping("/state")public ResponseObject<String> state(){ResponseObject<String> result=new ResponseObject<>();account++;stateHandles.getObject().handle();stateHandle.handle();return result;}@RequestMapping("/account")public String account(){return "account:"+account;}@PostConstructpublic void init(){System.out.println("init");}}

当我们在 Spring 中遇到 单例 Bean 注入原型 Bean 被共享的问题时,问题的根源在于 Spring 默认会在容器启动时实例化原型 Bean,并将同一个实例注入单例 Bean。为了解决这个问题,我们可以使用以下几种方法:

  1. 使用 ObjectProvider:延迟加载原型 Bean,每次调用时返回新的实例。
  2. 使用 ApplicationContext:通过 getBean() 动态获取原型 Bean,每次获取新的实例。
  3. 结合 @Scope("prototype")@Lazy 注解:延迟初始化原型 Bean,确保每次请求时创建新的实例。

这些解决方案能确保单例 Bean 中的原型 Bean 每次都创建新的实例,而不是共享同一个实例,从而解决了原型 Bean 被共享的问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/64349.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解决 OpenCV 与 FFmpeg 版本不兼容导致的编译错误

解决 OpenCV 与 FFmpeg 版本不兼容导致的编译错误 在安装并编译 OpenCV 3.2.0 版本时&#xff0c;出现的编译错误主要是由于 OpenCV 代码中使用的 FFmpeg 库宏定义与最新版 FFmpeg 库中的定义不一致所致。具体来说&#xff0c;原有的宏 CODEC_FLAG_GLOBAL_HEADER 和 AVFMT_RAW…

剑指offer搜索二维矩阵

题目连接 https://leetcode.cn/problems/search-a-2d-matrix-ii/’ 代码 自己想出来的 解法一 初始化两个指针&#xff0c;i0,j列数-1 若此时matrix[i][j]target 则返回true 若此时matrix[i][j]>target,表明在第j列中不可能存在target&#xff0c;因为列是升序的 若此时ma…

无管理员权限 LCU auth-token、port 获取(全网首发 go)

一&#xff1a; 提要&#xff1a; 参考项目&#xff1a; https://github.com/Zzaphkiel/Seraphine 想做一个 lol 查战绩的软件&#xff0c;并且满足自己的需求&#xff08;把混子和大爹都表示出来&#xff09;&#xff0c;做的第一步就是获取 lcu token &#xff0c;网上清一色…

STM32F407ZGT6-UCOSIII笔记6:UCOS-III软件定时器

今日学习使用UCOS系统的软件定时器功能 本文学习与程序编写基于 正点原子的 STM32F1 UCOS开发手册 文章提供测试代码讲解、完整工程下载、测试效果图 软件定时器卡柱UCOS III 系统问题解决 目录 UCOS-III 软件定时器&#xff1a; 单次定时器&#xff1a; 周期定时器: 开启软…

【网络安全】WIFI WPA/WPA2协议:深入解析与实践

WIFI WPA/WPA2协议&#xff1a;深入解析与实践 1. WPA/WPA2 协议 1.1 监听 Wi-Fi 流量 解析 WPA/WPA2 的第一步是监听 Wi-Fi 流量&#xff0c;捕获设备与接入点之间的 4 次握手数据。然而&#xff0c;设备通常不会频繁连接或重新连接&#xff0c;为了加速过程&#xff0c;攻…

【ubuntu18.04】ubuntu18.04挂在硬盘出现 Wrong diagnostic page; asked for 1 got 8解决方案

错误日志 [ 8754.700227] usb 2-3: new full-speed USB device number 3 using xhci_hcd [ 8754.867389] usb 2-3: New USB device found, idVendor0e0f, idProduct0002, bcdDevice 1.00 [ 8754.867421] usb 2-3: New USB device strings: Mfr1, Product2, SerialNumber0 [ 87…

金碟中间件-AAS-V10.0安装

金蝶中间件AAS-V10.0 AAS-V10.0安装 1.解压AAS-v10.0安装包 unzip AAS-V10.zip2.更新license.xml cd /root/ApusicAS/aas# 这里要将license复制到该路径 [rootvdb1 aas]# ls bin docs jmods lib modules templates config domains …

Reactor 响应式编程(第四篇:Spring Security Reactive)

系列文章目录 Reactor 响应式编程&#xff08;第一篇&#xff1a;Reactor核心&#xff09; Reactor 响应式编程&#xff08;第二篇&#xff1a;Spring Webflux&#xff09; Reactor 响应式编程&#xff08;第三篇&#xff1a;R2DBC&#xff09; Reactor 响应式编程&#xff08…

【Qt】信号、槽

目录 一、信号和槽的基本概念 二、connect函数&#xff1a;关联信号和槽 例子&#xff1a; 三、自定义信号和槽 1.自定义槽函数 2.自定义信号函数 例子&#xff1a; 四、带参的信号和槽 例子&#xff1a; 五、Q_OBJECT宏 六、断开信号和槽的连接 例子&#xff1a; …

数据库密码加密

数据库密码加密 简单来说&#xff1a; 我们会经常看到重置密码&#xff0c;小时候就会有疑惑&#xff0c;为什么不直接告诉我们密码&#xff0c;原来服务器自己也不知道。 我们都知道密码在数据库中不能明文&#xff0c;不然风险很高&#xff0c;有数据库权限的人还可能恶意利…

PCIE概述

PCIE概述 文章目录 PCIE概述前言一、应用场景二、PCIE理论2.1 硬件2.2 拓扑结构&#xff1a;处理器和设备之间的关系2.3 速率2.4 层次接口2.5 四种请求类型2.5.1 bar空间2.5.2 memory2.5.3 IO2.5.4 configuration2.5.5 message 前言 参考链接&#xff1a; pcie总线知识点解析 …

Android Studio创建新项目并引入第三方so外部aar库驱动NFC读写器读写IC卡

本示例使用设备&#xff1a;https://item.taobao.com/item.htm?spma21dvs.23580594.0.0.52de2c1bbW3AUC&ftt&id615391857885 一、打开Android Studio,点击 File> New>New project 菜单&#xff0c;选择 要创建的项目模版&#xff0c;点击 Next 二、输入项目名称…

NX系列-使用 `nmcli` 命令创建 Wi-Fi 热点并设置固定 IP 地址

使用 nmcli 命令创建 Wi-Fi 热点并设置固定 IP 地址 一、前言 在一些场景下&#xff0c;我们需要将计算机或嵌入式设备&#xff08;例如 NVIDIA Orin NX&#xff09;转换为 Wi-Fi 热点&#xff0c;以便其他设备&#xff08;如手机、笔记本等&#xff09;能够连接并使用该设备…

AngularJS 与 SQL 的集成应用

AngularJS 与 SQL 的集成应用 引言 在当今的Web开发领域,AngularJS 和 SQL 是两种非常重要的技术。AngularJS,作为一个强大的前端框架,能够帮助开发者构建复杂且高性能的客户端应用。而SQL(Structured Query Language),作为一种广泛使用的数据库查询语言,是管理关系型…

用docker快速安装电子白板Excalidraw绘制流程图

注&#xff1a;本文操作以debian12.8 最小化安装环境为host系统。 一、彻底卸载原有的残留 apt-get purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras 二、设置docker的安装源 # Add Dockers official G…

C++趟坑学习-new,delete,虚析构函数

#include <iostream> using namespace std;class Resource { public:Resource() { cout << "Resource constructed" << endl; }~Resource() { cout << "Resource destructed" << endl; } };int main() {// 动态分配一个包含…

Spring Mvc面试题(常见)

1 Spring MVC的执行流程 用户发起请求,请求先被Servlet拦截以后,转发给SpringMVC框架SpringMVC 里面的DispatcherServlet(核心控制器) 接收到请求,并转发给HandlerMappingHandlerMapping负责解析请求,根据请求信息和配置信息找到匹配的Controller类(当这里有配置拦截器,会…

mac 如何开启指定端口供外部访问?

前言 需要 mac 上开放指定端口&#xff0c;指定 ip 访问 解决 在 macOS 上开放一个端口&#xff0c;并指定只能特定的 IP 访问&#xff0c;可以使用 macOS 内置的 pfctl(Packet Filter)工具来实现。 1、 编辑 pf 配置文件&#xff1a; 打开 /etc/pf.conf 文件进行编辑。 可以使…

如何设置Jsoup解析京东商品详情?

在数字化时代&#xff0c;数据的价值日益凸显&#xff0c;尤其是在电商领域。通过爬虫技术&#xff0c;我们可以从网站中提取有价值的信息&#xff0c;用于市场分析、价格监控等。Java作为一种成熟且功能强大的编程语言&#xff0c;拥有丰富的库支持&#xff0c;使其成为编写爬…

STM32读写flash注意事项

STM32读写Flash时,需要注意以下事项以确保操作的正确性和可靠性: 一、写入操作注意事项 擦除操作: STM32内置Flash的写入操作必须遵循“先擦除,后写入”的原则。擦除操作以页(或扇区)为单位进行,这意味着在写入新数据之前,需要擦除整个页(或扇区)。写入单位: 写入操…