Spring进阶:掌控Bean的作用域与生命周期
在上一篇文章中,我们了解了Spring IoC容器如何接管对象的创建和依赖注入,实现了松耦合。容器创建并管理的对象,我们称之为Bean。
但是,容器仅仅是创建Bean就够了吗?显然不是。我们还需要关心:
-
这个Bean在容器中应该存在多少个实例? (是全局唯一,还是每次请求都创建一个新的?)
-
这个Bean从创建到销毁会经历哪些阶段? (我们能否在特定阶段执行一些自定义逻辑?)
这就是我们今天要探讨的核心内容:Bean的作用域(Scope) 和 Bean的生命周期(Lifecycle)。
一、Bean的作用域 (Scope):定义实例的存在范围
Bean的作用域定义了Spring容器根据Bean定义创建的实例数量以及这些实例的共享范围。简单来说,它决定了当你向容器请求一个Bean时,是返回一个已存在的共享实例,还是创建一个全新的实例。
Spring框架定义了多种作用域,最核心和常用的有以下几种:
-
Singleton (单例作用域 - 默认)
-
定义:在一个Spring IoC容器中,无论你请求多少次该Bean(通过getBean()或依赖注入),只会存在一个共享的Bean实例。
-
特点:容器启动时(非懒加载情况下)就会创建这个单例Bean,之后所有对该Bean的请求都会返回这同一个实例。它是Spring的默认作用域。
-
适用场景:无状态的Bean,如Service层对象、Repository层对象、工具类、配置类等。这些Bean通常不持有与特定请求相关的状态,可以被多线程安全地共享。
-
代码示例:
import org.springframework.stereotype.Service; // 默认就是singleton,可以省略 @Scope("singleton") // import org.springframework.context.annotation.Scope; // @Scope("singleton") @Service public class UserService {// ... 无状态的方法 ...public User findUser(long id) {System.out.println("Fetching user " + id + " using instance: " + this.hashCode());// ... 实际逻辑 ...return new User(id, "SingletonUser");} } -
注意:由于是共享实例,如果单例Bean持有可变状态(成员变量),必须特别注意线程安全问题。通常应设计为无状态或使用线程安全的方式管理状态(如ThreadLocal,或委托给其他有状态的prototype bean)。
-
-
Prototype (原型作用域)
-
定义:每次向Spring容器请求该Bean时,容器都会创建一个全新的Bean实例并返回。
-
特点:容器负责创建和注入依赖,但一旦将实例交给请求方,容器就不再跟踪和管理该实例的完整生命周期(特别是销毁阶段)。你需要自己负责后续的资源释放(如果需要)。
-
适用场景:有状态的Bean,即那些需要为每个请求或会话维护独立状态的对象。例如,一个代表用户购物车或者某个具体操作上下文的对象。
-
代码示例:
import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import org.springframework.beans.factory.config.ConfigurableBeanFactory; // 推荐使用常量@Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // 或者 @Scope("prototype") public class UserActionContext {private long userId;private String action;public UserActionContext() {System.out.println("Creating new UserActionContext instance: " + this.hashCode());}// Getters and Setters for state...public void setUserId(long userId) { this.userId = userId; }public void setAction(String action) { this.action = action; }// ... } -
注意:频繁创建和销毁prototype Bean可能会带来性能开销。同时,Spring容器不会自动调用prototype Bean的销毁回调方法(如@PreDestroy或DisposableBean.destroy),需要使用者自行管理。
-
-
Web应用专属作用域 (仅在Web环境有效)
-
Request: 每个HTTP请求都会创建一个新的Bean实例。该实例仅在当前HTTP请求内有效。
-
Session: 每个HTTP Session会创建一个新的Bean实例。该实例在当前HTTP Session内共享。
-
Application: 在整个Web应用(ServletContext)生命周期内,只创建一个Bean实例。类似于singleton,但作用范围是ServletContext。
-
WebSocket: 在WebSocket生命周期内,只创建一个Bean实例。
这些作用域在构建Web应用程序时非常有用,用于管理与请求、会话等相关的状态。我们将在后续的Web开发专题中更详细地探讨它们。使用它们需要你的应用是一个Web应用,并且进行了相应的配置(例如,在Spring MVC或Spring WebFlux环境)。
如何指定作用域?
-
注解方式: 使用@Scope注解,如 @Scope("prototype") 或 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)。
-
XML方式: 在<bean>标签中使用scope属性,如 <bean id="myBean" class="..." scope="prototype"/>。
-
二、Bean的生命周期 (Lifecycle):从诞生到消亡
Spring容器不仅创建Bean,还管理它们的整个生命周期,从实例化到最终销毁。理解这个过程,以及如何在关键节点介入,对于进行资源初始化、资源释放、逻辑验证等操作至关重要。
一个典型的(主要针对Singleton Bean)生命周期包含以下关键阶段:
-
实例化 (Instantiation): Spring容器根据Bean定义(XML、注解等)找到对应的类,通过Java反射机制调用构造函数创建Bean的实例。
-
填充属性 (Populate Properties): 容器分析Bean的依赖关系(DI),通过Setter方法或直接字段注入(或者构造器注入在实例化阶段完成)将依赖的Bean或其他配置值(如@Value注解的值)设置到Bean实例的属性中。
-
初始化 (Initialization): 这是Bean生命周期中一个非常重要的阶段,允许开发者执行自定义的初始化逻辑。Spring提供了多种方式介入:
-
执行Aware接口方法: 如果Bean实现了特定的Aware接口(如BeanNameAware, BeanFactoryAware, ApplicationContextAware),Spring会调用相应的方法,将容器自身的一些资源注入给Bean。例如,setBeanName()会在setBeanFactory()之前调用。
-
执行BeanPostProcessor前置处理: BeanPostProcessor接口的postProcessBeforeInitialization方法会被调用。这是一个全局性的扩展点,可以对容器中所有(或筛选后的)Bean进行处理。
-
执行初始化回调:
-
如果Bean实现了InitializingBean接口,其afterPropertiesSet()方法会被调用。
-
如果Bean定义中通过@Bean(initMethod="...")或XML的init-method属性指定了自定义初始化方法,该方法会被调用。
-
如果Bean的方法使用了@PostConstruct注解(JSR-250标准),该方法会被调用。这是目前推荐的方式,因为它不依赖Spring特定接口,更加标准。
(执行顺序: @PostConstruct -> InitializingBean.afterPropertiesSet -> init-method)
-
-
执行BeanPostProcessor后置处理: BeanPostProcessor接口的postProcessAfterInitialization方法会被调用。常用于对Bean进行代理包装(如AOP实现)。
-
-
Bean可用 (Bean is Ready): 经过以上步骤,Bean实例已经完全创建并初始化好,可以被应用程序使用了。对于Singleton Bean,它会驻留在容器的单例缓存中。
-
销毁 (Destruction): 当Spring容器关闭时(或者对于非Singleton作用域,在特定条件下),容器会管理Bean的销毁过程。同样提供了多种回调机制:
-
执行销毁回调:
-
如果Bean实现了DisposableBean接口,其destroy()方法会被调用。
-
如果Bean定义中通过@Bean(destroyMethod="...")或XML的destroy-method属性指定了自定义销毁方法,该方法会被调用。
-
如果Bean的方法使用了@PreDestroy注解(JSR-250标准),该方法会被调用。这也是目前推荐的方式。
(执行顺序: @PreDestroy -> DisposableBean.destroy -> destroy-method)
-
-
生命周期回调示例 (@PostConstruct 和 @PreDestroy)
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;@Component // 默认是 singleton
public class ResourceHandler {public ResourceHandler() {System.out.println("1. Constructor: ResourceHandler instance created.");}@PostConstruct // 初始化回调public void init() {System.out.println("2. @PostConstruct: Initializing resources...");// 例如:建立数据库连接、加载配置文件、启动后台线程等}public void useResource() {System.out.println("3. Using ResourceHandler...");}@PreDestroy // 销毁回调public void cleanup() {System.out.println("4. @PreDestroy: Cleaning up resources...");// 例如:关闭数据库连接、释放文件句柄、停止后台线程等}
}// --- 在Spring应用中 ---
// ApplicationContext context = ... ;
// ResourceHandler handler = context.getBean(ResourceHandler.class);
// handler.useResource();
// ((ConfigurableApplicationContext) context).close(); // 关闭容器时会触发 @PreDestroy
输出大致顺序:
-
Constructor: ResourceHandler instance created.
-
@PostConstruct: Initializing resources...
-
Using ResourceHandler...
-
(当容器关闭时) @PreDestroy: Cleaning up resources...
注意:
-
prototype作用域的Bean,Spring容器在创建并交给调用者后,不会负责其后续的销毁回调(@PreDestroy, DisposableBean, destroy-method)。如果prototype Bean需要释放资源,需要调用者手动处理,或者使用BeanPostProcessor等高级技巧来管理。
三、作用域与生命周期的交互
-
Singleton Bean: 经历完整的生命周期,由Spring容器严格管理其创建、初始化和销毁。
-
Prototype Bean: 每次请求都创建新实例,执行实例化、属性填充、初始化回调(如@PostConstruct),然后交给请求方。销毁阶段不由容器管理。
-
Web作用域 Bean: 生命周期与对应的Web范围(Request、Session等)绑定。当范围结束时,容器负责销毁这些Bean并执行销毁回调。
四、为什么理解作用域和生命周期很重要?
-
资源管理: 通过生命周期回调(特别是@PostConstruct和@PreDestroy),可以在Bean创建后初始化资源(如连接池、文件句柄),在Bean销毁前释放资源,避免内存泄漏或资源枯竭。
-
状态管理: 正确选择作用域(singleton vs prototype等)是管理Bean状态的关键。误用singleton处理每个请求都不同的状态会导致数据错乱和线程安全问题。
-
性能考量: singleton性能较好(实例复用),而prototype涉及实例创建销毁开销。需要根据场景权衡。
-
调试与问题排查: 了解Bean的创建和销毁过程有助于定位配置错误、循环依赖、初始化失败等问题。
-
框架扩展: BeanPostProcessor等生命周期接口是Spring框架实现AOP、事务管理等功能的基础,理解生命周期有助于理解这些高级特性。
五、总结
Spring Bean的作用域(Scope)定义了Bean实例的共享策略和存在范围(如singleton, prototype),而生命周期(Lifecycle)则描述了Bean从创建到销毁所经历的各个阶段以及开发者可以介入的时机(通过@PostConstruct, @PreDestroy等回调)。
熟练掌握这两个概念,能够让你更精确地控制Bean的行为,更有效地管理资源,编写出更健壮、高效的Spring应用程序。它们是深入理解Spring内部机制和进行高级开发的必备知识。
相关文章:
Spring进阶:掌控Bean的作用域与生命周期
在上一篇文章中,我们了解了Spring IoC容器如何接管对象的创建和依赖注入,实现了松耦合。容器创建并管理的对象,我们称之为Bean。 但是,容器仅仅是创建Bean就够了吗?显然不是。我们还需要关心: 这个Bean在容…...
【Leetcode-Hot100】移动零
题目 解答 首先,使用的解题思路是:使用两个指针,分别指向数组的第一个0元素位置,以该元素位置1为起始点寻找接下来第一个非0元素位置。二者确定后,对其进行交换。随后继续寻找下一个0元素位置。重复上述操作。 但第一…...
安装 Calico 的两种主流方式对比
本文对比了 Calico 的两种主流安装方式: 使用 calico.yaml 的 Manifest 安装方式使用 Tigera Operator(tigera-operator.yaml custom-resources.yaml)安装方式 ✅ 1. 使用 Manifest 方式安装(直接部署 calico.yaml) …...
leetcode_203. 移除链表元素_java
203. 移除链表元素https://leetcode.cn/problems/remove-linked-list-elements/ 1、题目 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val val 的节点,并返回 新的头节点 。 示例 1: 输入:head …...
常见算法模板总结
文章目录 一、二叉树1. DFS2. BFS 二、回溯模板三、记忆化搜索四、动态规划1. 01背包朴素版本滚动数组优化 2. 完全背包朴素版本滚动数组优化 3. 最长递增子序列LIS朴素版本贪心二分优化 4. 最长公共子序列5. 最长回文子串 五、滑动窗口六、二分查找七、单调栈八、单调队列九、…...
UE5学习笔记 FPS游戏制作44 统一UI大小 sizeBox
如果我们希望多个类似的UI大小一样,例如不同菜单的标题,可以使用sizeBox组件 我们在标题控件上,用sizeBox包裹所有子物体 然后指定他的最小宽高,或最大宽高 如果指定的是最小宽高,当子元素(如图片…...
# 基于BERT的文本分类
基于BERT的文本分类项目的实现 一、项目背景 该文本分类项目主要是情感分析,二分类问题,以下是大致流程及部分代码示例: 二、数据集介绍 2.1 数据集基本信息 数据集自定义类型二分类(正面/负面)样本量训练集 验证…...
C++学习之服务器EPOLL模型、处理客户端请求、向客户端回复数、向客户端发送文件
目录 1.启动epoll模型 2.和客户端建立新连接 3.接受客户端Http请求数据 4.代码回顾从接受的数据中读出请求行 5.请求行解析 6.正则表达式以及匹配 7.解析请求行以及后续处理 8.对path处理说明 9.如何回复响应数据 10.对文件对应content-type如何查询 11.服务器处理流…...
BUUCTF-web刷题篇(17)
26.BabyUpload 源码:https://github.com/imaginiso/GXY_CTF/tree/master/Web/babyupload 查看题目源码: 写着:SetHandler application/x-httpd-php 通过源码可以看出这道文件上传题目主要还是考察.htaccess配置文件的特性,倘若…...
国网B接口协议调阅实时视频接口流程详解以及检索失败原因(电网B接口)
文章目录 一、B接口协议调阅实时视频接口介绍B.6.1 接口描述B.6.2 接口流程B.6.3 接口参数B.6.3.1 SIP头字段B.6.3.2 SIP响应码B.6.3.3 SDP参数定义B.6.3.4 RTP动态Payload定义 B.6.4 消息示例B.6.4.1 调阅实时视频请求B.6.4.2 调阅实时视频请求响应 二、B接口调阅实时视频失败…...
windows11下pytorch(cpu)安装
先装anaconda 见最下方 Pytorch 官网:PyTorch 找到下图(不要求版本一样)(我的电脑是集显(有navdia的装gpu),装cpu) 查看已有环境列表 创建环境 conda create –n 虚拟环境名字(…...
NVR接入录像回放平台用EasyCVR打造地下车库安防:大型商居安全优选方案
一、背景分析 随着居民生活品质的提升,大型商业建筑和住宅小区纷纷配套建设地下停车库。但是地下车库盗窃、失火、恶意毁坏车辆、外部人员随意进出等事件频发,部署视频监控系统成为保障地下车库的安全关键举措。 目前,很多商业和住宅都会在…...
玻璃期货数据下载与分析:Python金融实战分享
期货数据下载与分析:Python实战分享 引言 在金融市场中,期货分析是一项重要的工作,而获取准确且及时的数据是进行有效分析的基础。今天,我们将深入探讨一段使用Python编写的代码,该代码用于从郑州商品交易所…...
excel常见错误包括(#N/A、#VALUE!、#REF!、#DIV/0!、#NUM!、#NAME?、#NULL! )
目录 1. #N/A2. #VALUE!3. #REF!4. #DIV/0!5. #NUM!6. #NAME?7. #NULL!8.图表总结 在 Excel 中,可能会遇到以下常见的错误值,每个都有特定的含义和成因: 1. #N/A 含义: 表示“Not Available”(不可用)。…...
乾元通渠道商中标川藏铁路西藏救援队应急救援装备项目
乾元通渠道商中标川藏铁路西藏救援队应急救援装备项目,项目内通信指挥车基于最新一代应急指挥车解决方案打造,配合乾元通自研的车载多链路聚合路由及系统,主要用途为保障应急通讯,满足任务执行时指挥协调、通信联络及数据传输的要…...
数学知识——矩阵乘法
使用矩阵快速幂优化递推问题 对于一个递推问题,如递推式的每一项系数都为常数,我们可以使用矩阵快速幂来对算法进行优化。 一般形式为: F n F 1 A n − 1 F_nF_1A^{n-1} FnF1An−1 由于递推式的每一项系数都为常数,因此对…...
左右开弓策略思路
一、策略概述 本策略是一种基于多种技术指标的复杂交易策略,包括自定义指标计算、过滤平滑处理以及交易信号生成。 该策略通过不同的交易平台代码段实现,旨在通过分析历史价格数据来预测未来价格走势,并据此生成交易信号。 二、主要技术指标…...
将jar包制作成deb一键安装包
文章目录 准备环境准备deb包结构构建Deb包测试安装常用操作命令 本文介绍如何将java运行环境、jar程序一起打包成一个deb格式的安装包,创建桌面图标,通过点击图标可使用系统自带浏览器快捷访问web服务的URL,同时注册服务并配置好开机自启。 准…...
Java 常用安全框架的 授权模型 对比分析,涵盖 RBAC、ABAC、ACL、基于权限/角色 等模型,结合框架实现方式、适用场景和优缺点进行详细说明
以下是 Java 常用安全框架的 授权模型 对比分析,涵盖 RBAC、ABAC、ACL、基于权限/角色 等模型,结合框架实现方式、适用场景和优缺点进行详细说明: 1. 授权模型类型与定义 模型名称定义特点RBAC(基于角色的访问控制)通…...
aws平台练习
注册 AWS 账户 访问 AWS 官方网站,点击“免费注册”按钮,按照提示完成账户注册: 提供电子邮件地址、密码和电话号码。 验证身份(可能需要手机验证码)。 设置 billing 信息。 2. 登录 AWS 管理控制台 使用注册的邮箱和…...
力扣DAY40-45 | 热100 | 二叉树:直径、层次遍历、有序数组->二叉搜索树、验证二叉搜索树、二叉搜索树中第K小的元素、右视图
前言 简单、中等 √ 好久没更了,感觉二叉树来回就那些。有点变懒要警醒,不能止步于笨方法!! 二叉树的直径 我的题解 遍历每个节点,左节点最大深度右节点最大深度当前节点当前节点为中心的直径。如果左节点深度更大…...
【MYSQL从入门到精通】数据类型及建表
一些基础操作语句 1.使用客户端工具连接数据库服务器:mysql -uroot -p 2.查看所有数据库:show databases; 3.创建属于自己的数据库: create database 数据库名;create database if not exists 数据库名; 强烈建议大家在建立数据库时指定编…...
【动态规划】 深入动态规划—两个数组的dp问题
文章目录 前言例题一、最长公共子序列二、不相交的线三、不同的子序列四、通配符匹配五、交错字符串六、两个字符串的最小ASCII删除和七、最长重复子数组 结语 前言 问题本质 它主要围绕着给定的两个数组展开,旨在通过对这两个数组元素间关系的分析,找出…...
结合大语言模型整理叙述并生成思维导图的思路
楔子 我比较喜欢长篇大论。这在代理律师界被视为一种禁忌。 我高中一年级的时候因为入学成绩好(所在县榜眼名次),直接被所在班的班主任任命为班长。我其实不喜欢这个岗位。因为老师一来就要提前注意到,要及时喊“起立”、英语课…...
Kotlin学习
kotlin android 开源,Kotlin开源项目集合_晚安 呼-华为开发者空间 干货来袭,推荐几款开源的Kotlin的Android项目 https://zhuanlan.zhihu.com/p/536789267 【已解决】 ubuntu apt-get update连不上dl.google.com_为什么不能ping谷歌-CSDN博客...
若依前后端分离版本从mysql切换到postgresql数据库
一、修改依赖: 修改admin模块pom.xml中的依赖,屏蔽或删除mysql依赖,增加postgresql依赖。 <!-- Mysql驱动包 --> <!--<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId> &l…...
【力扣hot100题】(073)数组中的第K个最大元素
花了两天时间搞明白答案的快速排序和堆排序。 两种都写了一遍,感觉堆排序更简单很多。 两种都记录一下,包括具体方法和易错点。 快速排序 class Solution { public:vector<int> nums;int quicksort(int left,int right,int k){if(leftright) r…...
【AAOS】【源码分析】CarAudioService(二)-- 功能介绍
汽车音频是 Android 汽车操作系统 (AAOS) 的一项功能,允许车辆播放信息娱乐声音,例如媒体、导航和通信。AAOS 不负责具有严格可用性和时间要求的铃声和警告,因为这些声音通常由车辆的硬件处理。将汽车音频服务集成在汽车中,彻底改变了驾驶体验,为驾驶员和乘客提供了音乐、…...
mapbox基础,加载F4Map二维地图
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.2 ☘️mapboxgl.Map style属性二、🍀F4Map 简介2.1 ☘️技术特点2.2 ☘️核…...
Android:Android Studio右侧Gradle没有assembleRelease等选项
旧版as是“Do not build Gradle task list during Gradle sync” 操作这个选项。 参考这篇文章:Android Studio Gradle中没有Task任务,没有Assemble任务,不能方便导出aar包_gradle 没有task-CSDN博客 在as2024版本中,打开Setting…...
