SpringBoot:详解Bean生命周期和作用域
《RabbitMQ》《Spring》《SpringMVC》
文章目录
- 前言
- 一、生命周期
- 二、作用域
- 总结
前言
前面我们讲诉了将Bean正确地装配到IoC容器,却未讲诉IoC如何装配和销毁Bean。本篇文章主要讲诉一下Bean的生命周期和作用域。
一、生命周期
Bean 的生命周期的过程, 它大致分为Bean定义、Bean 的初始化、 Bean 的生存期和 Bean 的销毁4个部分。 其中 Bean 定义过程大致如下:
- Spring 通过我们的配置,如@ComponentScan 定义的扫描路径去找到带有@Component 的类,
这个过程就是一个资源定位的过程。- 一旦找到了资源,那么它就开始解析,并且将定义的信息保存起来。注意,此时还没有初始
化Bean,也就没有Bean 的实例,它有的仅仅是Bean 的定义。- 然后就会把Bean 定义发布到 Spring IoC 容器中。 此时, IoC 容器也只有Bean 的定义,还是
没有Bean 的实例生成。完成了这3 步只是一个资源定位并将Bean 的定义发布到IoC容器的过程,还没有Bean实例的生成,更没有完成依赖注入。在默认的情况下, Spring会继续去完成Bean 的实例化和依赖注入,这样从IoC 容器中就可以得到一个依赖注入完成的Bean。 但是,有些Bean会受到变化因素的影响,这时我们倒希望是取出 Bean 的时候完成初始化和依赖注入,换句话说就是让那些 Bean 只是将定义发布到IoC 容器而不做实例化和依赖注入, 当我们取出来的时候才做初始化和依赖注入等操作。
Spring Bean的初始化过程:
ComponentScan 中还有一个配置项 lazyI nit,只可以配置 Boolean 值,且默认值为 false,也就是默认不进行延迟初始化,因此在默认的情况下Spring会对Bean进行实例化和依赖注入对应的属性值。
引入例子:人类(Person)有时候利用一些动物(Animal)去完成一些事情,比方说狗(Dog)是用来看门的,猫(Cat)是用来抓老鼠的.。
代码如下:
//定义人类接口
public interface Person {void service();void setAnimal(Animal animal);
}
//定义动物接口
public interface Animal {void user();
}
//定义狗
@Component
public class Dog implements Animal {@Overridepublic void user() {System.out.println("狗【" + Dog.class.getSimpleName() + "】是用来看门的");}
}
//定义年轻人
@Component
public class YoungPerson implements Person {@Autowiredprivate Animal animal = null;@Overridepublic void service() {this.animal.user();}@Overridepublic void setAnimal(Animal animal) {this.animal = animal;}
}
//定义猫
@Component
public class Cat implements Animal{@Overridepublic void user() {System.out.println("猫【" + Cat.class.getSimpleName() + "】是抓老鼠的");}
}//定义配置类
@Configuration
@ComponentScan("com.dragon.restart")//所有的包和类都在restart下
public class AppConfig {
}
此时没有配置lazyInit的情况进行断点测试如下:
可以看到在断点处,我们并没有获取Bean 的实例,而日志就已经打出了,可见它是在SpringIoC容器初
始化时就执行了实例化和依赖注入。为了改变这个情况,我们在配置类AppConfig的@ComponentScan
中加入lazylnit 配置,如下面的代码:
@Configuration
@ComponentScan(value = "com.dragon.restart",lazyInit = true)
public class AppConfig {
}
就可以发现在断点处“延迟依赖注入”这行并不会出现在日志中,只有运行过断点处才会出现这行日志,这是因为我们把它修改为了延迟初始化, Spring并不会在发布Bean定义后马上为我们完成实例化和依赖注入。
如果仅仅是实例化和依赖注入还是比较简单的,还不能完成进行自定义的要求。 为了完成依赖注入的功能, Spring 在完成依赖注入之后,还提供了一系列的接口和配置来完成Bean初始化的过程,让我们学习这个过程。 Spring在完成依赖注入后,还会进行如下图所示流程来完成它的生命周期:
图中描述的是整个IoC容器初始化Bean 的流程,作为开发者,需要注意这些流程。除此之外,还需要注意以下两点:
- 这些接口和方法是针对什么而言的。 对于上图, 在没有注释的情况下的流程节点都是针对单个Bean 而言的,但是BeanPostProcessor 是针对所有 Bean 而言的,这是我们需要注意的地方。
- 即使你定义了 ApplicationContextAware 接口,但是有时候并不会调用,这要根据你的 IoC 容器来决定。 我们知道, Spring IoC 容器最低的要求是实现 BeanFactory 接口,而不是实现ApplicationContext 接口 。 对于那些没有实现 ApplicationContext 接口的容器,在生命周期对应的ApplicationContextAware 定义的方法也是不会被调用的,只有实现了 ApplicationContext 接口的容器,才会在生命周期调用 ApplicationContextAware 所定义的 setApplicationContext方法。
现在改造一下YoungPerson类:
@Component
public class YoungPerson implements Person, BeanNameAware , BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean {private Animal animal = null;@Overridepublic void service() {this.animal.user();}@Autowired@Qualifier("dog")@Overridepublic void setAnimal(Animal animal) {System.out.println("延迟依赖注入");this.animal = animal;}@Overridepublic void setBeanName(String name) {System.out.println ("【" + this.getClass().getSimpleName() + "】调用BeanNameAware的setBeanName");}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {System.out.println ("【" + this.getClass().getSimpleName() + "】调用BeanFactoryAware的setBeanFactory");}@Overridepublic void destroy() throws Exception {System.out.println ("【" + this.getClass().getSimpleName() + "】调用DisposableBean方法");}@Overridepublic void afterPropertiesSet() throws Exception {System.out.println ("【" + this.getClass().getSimpleName() + "】调用InitializingBean方法的afterPropertiesSet方法");}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {System.out.println ("【" + this.getClass().getSimpleName() + "】调用ApplicationContextAware方法的setApplicationContext方法");}@PostConstructpublic void init () {System.out.println("【" + this.getClass().getSimpleName() + "】注解@PostConstruct定义的自定义初始化方法");}@PreDestroypublic void destroyl () {System.out.println("【" + this.getClass().getSimpleName() + "】注解@PreDestroy定义的自定义销毁方法");}
}
这样,这个 B巳an 就实现了生命周期中单个 Bean 可以实现的所有接口, 并且通过注解@PostConstruct 定义了初始化方法,通过注解@PreDestroy 定义了销毁方法。 为了测试 Bean 的后置处理器, 这里创建一个类BeanPostProcessorExampIe,如下:
/*** @Version: 1.0.0* @Author: Dragon_王* @ClassName: BeanPostProcessorExample* @Description: TODO描述* @Date: 2024/1/20 23:34*/
public class BeanPostProcessorExample implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println("BeanPostProcessor调用"+"postProcessBeforeinitialization方法,参数【"+bean.getClass().getSimpleName()+"】【"+beanName+"】");return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("BeanPostProcessor调用"+"postProcessAfterinitialization方法,参数【"+bean.getClass().getSimpleName()+"】【"+beanName+"】");return bean;}
}
注意,这个Bean后置处理器将对所有的Bean有效,运行测试如下:
测试类:
AnnotationConfigApplicationContext ctx =new AnnotationConfigApplicationContext(AppConfig.class) ;ctx.close();
2024-01-20T23:43:23.135+08:00 INFO 748 --- [ main] c.d.restart.RestartApplicationTests : Starting RestartApplicationTests using Java 19 with PID 748 (started by ThundeRobot in E:\IDEA_projects\restart)
2024-01-20T23:43:23.136+08:00 INFO 748 --- [ main] c.d.restart.RestartApplicationTests : No active profile set, falling back to 1 default profile: "default"
BeanPostProcessor调用postProcessBeforeinitialization方法,参数【RestartApplication$$SpringCGLIB$$0】【restartApplication】
BeanPostProcessor调用postProcessAfterinitialization方法,参数【RestartApplication$$SpringCGLIB$$0】【restartApplication】
BeanPostProcessor调用postProcessBeforeinitialization方法,参数【AppConfig$$SpringCGLIB$$0】【appConfig】
BeanPostProcessor调用postProcessAfterinitialization方法,参数【AppConfig$$SpringCGLIB$$0】【appConfig】
BeanPostProcessor调用postProcessBeforeinitialization方法,参数【Cat】【cat】
BeanPostProcessor调用postProcessAfterinitialization方法,参数【Cat】【cat】
BeanPostProcessor调用postProcessBeforeinitialization方法,参数【Dog】【dog】
BeanPostProcessor调用postProcessAfterinitialization方法,参数【Dog】【dog】
延迟依赖注入
【YoungPerson】调用BeanNameAware的setBeanName
【YoungPerson】调用BeanFactoryAware的setBeanFactory
【YoungPerson】调用ApplicationContextAware方法的setApplicationContext方法
BeanPostProcessor调用postProcessBeforeinitialization方法,参数【YoungPerson】【youngPerson】
【YoungPerson】注解@PostConstruct定义的自定义初始化方法
【YoungPerson】调用InitializingBean方法的afterPropertiesSet方法
BeanPostProcessor调用postProcessAfterinitialization方法,参数【YoungPerson】【youngPerson】
BeanPostProcessor 调用 postProcessBeforeinitialization 方法,参数 【Cat】【cat】BeanPostProcessor 调用 postProcessAfterinitialization 方法, 参数 【Cat】【cat】2024-01-20T23:43:24.044+08:00 INFO 748 --- [main] c.d.restart.RestartApplicationTests : Started RestartApplicationTests in 1.142 seconds (process running for 1.772)
【YoungPerson】注解@PreDestroy定义的自定义销毁方法
【YoungPerson】调用DisposableBean方法
从日志可以看出,对于Bean后置处理器(BeanPostProcessor)而言, 它对所有的 Bean 都起作用,而其他的接口则是对于单个Bean起作用。我们还可以注意到BussinessPerson执行的流程是上图所画出的流程。有时候Bean 的定义可能使用的是第三方的类,此时可以使用注解@Bean来配置自定义初始化和销毁方法,如下所示:
@Bean(InitMethod =”Init”, destroyMethod = ”destroy” )
二、作用域
在介绍IoC 容器最顶级接口 BeanFactory 的时候, 可以看到 isSingleton 和 isPrototype 两个方法。其中,isSingleton 方法如果返回 true,则 Bean 在 loC 容器中以单例存在,这也是 Spring IoC 容器的默认值;如果 isPrototype 方法返回 true,则当我们每次获取 Bean 的时候, IoC 容器都会创建一个新的 Bean,这显然存在很大的不同,这便是Spring Bean 的作用域的问题。在一般的容器中, Bean都会存在单例(Singleton)和原型(Prototype)两种作用域, Java EE 广泛地使用在互联网中,而在 Web容器中, 则存在页面(page)、请求(request)、会话 (session)和应用(application) 4 种作用域。对于页面(page),是针对 JSP 当前页面的作用域,所以 Spring是无法支持的。为了满足各类的作用域,在Spring 的作用域中就存在如表所示的几种类型。
作用域类型 | 使用范围 | 作用域描述 |
---|---|---|
singleton | 所有Spring 应用 | 默认值, loC 容器只存在单例 |
prototype | 所有Spring 应用 | 每当从IoC 容器中取出一个 Bean,则创建一个新的Bean |
session | Spring Web 应用 | HTTP 会话 |
application | Spring Web 应用 | Web 工程生命周期 |
request | Spring Web 应用 | Web 工程单次请求 (request) |
globalSession | Spring Web 应用 | 在一个全局的HTTPSession 中, 一个 Bean 定义对应一个实例。 实践中基本不使用 |
- 前四个最常用
- 对于application作用域,完全可以使用单例来替代。
下面我们探讨单例 (Singleton)和原型(prototype)的区别
首先定义一个类
@Component
//@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ScopeBean { }
这是一个简单的类, 可以看到这里声明作用域的代码已经被注释掉了, 这样就是启用默认的作用域,实际就是单例。为了证明作用域的存在,我们进行一下测试:
AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext (AppConfig.class);ScopeBean scopeBeanl = ctx.getBean (ScopeBean.class);ScopeBean scopeBean2 = ctx.getBean (ScopeBean .class);System.out.println (scopeBeanl == scopeBean2) ;
从测试的结果来看,显然scopeBeanl 和 scopeBean2 这两个变量都指向了同一的实例,所以在IoC容器中, 只有一个ScopeBean 的实例。 然后取消代码中作用域代码的注释,进行同样的测试, 则可以看到scopeBeanl == scopeBean2 返回的将是 false,而不再是 true, 那是因为我们将Bean 的作用域修改为了 prototype,这样就能让IoC 容器在每次获取Bean 时,都新建一个Bean的实例返回给调用者。
这里的 ConfigurableBeanFactory 只能提供单例 ( SCOPE_ SINGLETON )和原型 ( SCOPE_PROTOTYPE)两种作用域供选择, 如果是在 SpringMVC环境中,还可以使用 WebApplicationContext去定义其他作用域, 如请求(SCOPE REQUEST)、 会话 (SCOPE_SESSION) 和应用 (SCOPE_APPLICATION)。 例如,下面的代码就是定义请求作用域:
@Component
@Scope(WebApplicationContext.SCOPE_REQUEST)
public class ScopeBean { }
总结
以上就是Bean生命周期和作用域的讲解。
相关文章:

SpringBoot:详解Bean生命周期和作用域
🏡浩泽学编程:个人主页 🔥 推荐专栏:《深入浅出SpringBoot》《java项目分享》 《RabbitMQ》《Spring》《SpringMVC》 🛸学无止境,不骄不躁,知行合一 文章目录 前言一、生命周期二…...

【图解数据结构】顺序表实战指南:手把手教你详细实现(超详细解析)
🌈个人主页:聆风吟 🔥系列专栏:图解数据结构、算法模板 🔖少年有梦不应止于心动,更要付诸行动。 文章目录 一. ⛳️线性表1.1 🔔线性表的定义1.2 🔔线性表的存储结构 二. ⛳️顺序表…...

WordPress怎么禁用文章和页面古腾堡块编辑器?如何恢复经典小工具?
现在下载WordPress最新版来搭建网站,默认的文章和页面编辑器,以及小工具都是使用古腾堡编辑器(Gutenberg块编辑器)。虽然有很多站长说这个编辑器很好用,但是仍然有很多站长用不习惯,觉得操作太难了…...

【HarmonyOS】掌握布局组件,提升应用体验
从今天开始,博主将开设一门新的专栏用来讲解市面上比较热门的技术 “鸿蒙开发”,对于刚接触这项技术的小伙伴在学习鸿蒙开发之前,有必要先了解一下鸿蒙,从你的角度来讲,你认为什么是鸿蒙呢?它出现的意义又是…...
第4周:Pytorch——综合应用和实战项目 Day 28-30: 学习资源和社区参与
第4周:综合应用和实战项目 Day 28-30: 学习资源和社区参与 在这个阶段,我们将探索更多的学习资源并鼓励参与PyTorch和TensorFlow的社区,以进一步提升技术和融入开发者社群。 学习资源: 论文:阅读最新的机器学习和深度…...

TypeScript教程(一)在vscode中的配置TypeScript环境
TypeScript教程(一)在vscode中的配置TypeScript环境 文章目录 TypeScript教程(一)在vscode中的配置TypeScript环境一、前言二、具体步骤1、Node.js安装2、TypeScript安装3、helloworld 一、前言 未来的开发者们请上座,…...

sshpass的安装与使用
一.简介 1.定义: ssh 登陆不能在命令行中指定密码,sshpass 的出现则解决了这一问题。它允许你用 -p 参数指定明文密码,然后直接登录远程服务器,它支持密码从命令行、文件、环境变量中读取。 2.使用 sshpass 原因 使用 sshpass…...

Excel·VBA合并工作簿2
其他合并工作簿的方法,见之前的文章《ExcelVBA合并工作簿》 目录 8,合并文件夹下所有工作簿中所有工作表,按表头汇总举例 8,合并文件夹下所有工作簿中所有工作表,按表头汇总 与之前的文章《ExcelVBA合并工作簿&#x…...

linux内核原理--分页,页表,内核线性地址空间,伙伴系统,内核不连续页框分配,内核态小块内存分配器
1.分页,页表 linux启动阶段,最初运行于实模式,此阶段利用段寄存器,段内偏移,计算得到物理地址直接访问物理内存。 内核启动后期会切换到保护模式,此阶段会开启分页机制。一旦开启分页机制后,内…...

【MongoDB】下载安装、指令操作
目录 1.下载安装 2.指令 2.1.基础操作指令 2.2.增加 2.3.查询 2.4.修改 2.5.删除 前言: 关于MongoDB的核心概念请移步: 【文档数据库】ES和MongoDB的对比-CSDN博客 1.下载安装 本文以安装Windows版本的mongodb为例,Linux版本的其实…...
k8s-pvc/pv扩容记录
背景 一次聊天过程中,对方提及pvc的扩容,虽然有注意过 storageclass 有个AllowVolumeExpansion的配置(有些csi插件是不支持该配置的,比如local-volume-provisoner),但是没有实际用过,所以还是心…...
关于Unity插件TriLib使用的一点儿心得
最近做一个项目的时候,由于要求动态加载fbx或者glb等格式文件,而我们自己开发加载插件难度又有点大,所以最后使用了TriLib这个插件,现在说一点使用心得。 由于文件加载之后要对加载的内容进行复制,比如加载一个柱子&am…...
计算机二级Python基本排序题-序号45(补充)
1. 文件"singup.txt”中保存了若干条参加运动会学生的报名记录,每条记录的形式为“班级号_学号”,例如"A1_12”,将每个班级报名情按参加运动会人数从多到少排列(假设不存在人数相同的情况)并输出,…...

响应式Web开发项目教程(HTML5+CSS3+Bootstrap)第2版 例4-6 fieldset
代码 <!doctype html> <html> <head> <meta charset"utf-8"> <title>fieldset</title> </head><body> <form action"#"><fieldset><legend>学生信息</legend>姓名:&…...
html渲染优先级
在前端开发中,优先布局是指在设计和构建页面时,将页面的各个部分按照其重要性和优先级进行排序,并依次进行布局和开发。这种方法可以帮助开发团队在项目初期就确定页面结构的核心部分,从而更好地掌控项目的整体进度和优先级。且确…...

linux 更新镜像源
打开终端,备份一下旧的 源 文件,以防万一 cd /etc/apt/ ls sudo cp sources.list sources.list.bak ls然后打开清华大学开源软件镜像站 搜索一下你的linux发行版本,我这里是ubuntu发行版本 点击这个上面图中的问号 查看一下自己的版本号&a…...

【征服Redis12】redis的主从复制问题
从现在开始,我们来讨论redis集群的问题,在前面我们介绍了RDB和AOF两种同步机制,那你是否考虑过这两个机制有什么用呢?其中的一个重要作用就是为了集群同步设计的。 Redis是一个高性能的键值存储系统,广泛应用于Web应用…...
php函数 一
一 自动加载 1.1 __autoload(string $class) 类自动加载,7.2版本之后废弃。可使用sql_autoload_register()注册方法实现。 类自动加载,无返回值。 #php7.2之前function __autoload($class) {if(strpos($class, CI_) ! 0){if (file_exists(APPPATH . …...
监督学习 - 梯度提升回归(Gradient Boosting Regression)
什么是机器学习 梯度提升回归(Gradient Boosting Regression)是一种集成学习方法,用于解决回归问题。它通过迭代地训练一系列弱学习器(通常是决策树)来逐步提升模型的性能。梯度提升回归的基本思想是通过拟合前一轮模…...

【工具】使用ssh进行socket5代理
文章目录 shellssh命令详解正向代理:反向代理:本地 socks5 代理 shell ssh -D 3333 root192.168.0.11 #输入密码 #3333端口已经使用远程机进行转发设置Windows全局代理转发 socks127.0.0.1 3333如果远程机为公网ip,可通过搜索引擎查询出网…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...

Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...

均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...

push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...

Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践
在 Kubernetes 集群中,如何在保障应用高可用的同时有效地管理资源,一直是运维人员和开发者关注的重点。随着微服务架构的普及,集群内各个服务的负载波动日趋明显,传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…...

C++--string的模拟实现
一,引言 string的模拟实现是只对string对象中给的主要功能经行模拟实现,其目的是加强对string的底层了解,以便于在以后的学习或者工作中更加熟练的使用string。本文中的代码仅供参考并不唯一。 二,默认成员函数 string主要有三个成员变量,…...
无需布线的革命:电力载波技术赋能楼宇自控系统-亚川科技
无需布线的革命:电力载波技术赋能楼宇自控系统 在楼宇自动化领域,传统控制系统依赖复杂的专用通信线路,不仅施工成本高昂,后期维护和扩展也极为不便。电力载波技术(PLC)的突破性应用,彻底改变了…...

理想汽车5月交付40856辆,同比增长16.7%
6月1日,理想汽车官方宣布,5月交付新车40856辆,同比增长16.7%。截至2025年5月31日,理想汽车历史累计交付量为1301531辆。 官方表示,理想L系列智能焕新版在5月正式发布,全系产品力有显著的提升,每…...

虚拟机网络不通的问题(这里以win10的问题为主,模式NAT)
当我们网关配置好了,DNS也配置好了,最后在虚拟机里还是无法访问百度的网址。 第一种情况: 我们先考虑一下,网关的IP是否和虚拟机编辑器里的IP一样不,如果不一样需要更改一下,因为我们访问百度需要从物理机…...