6. Bean 的作用域和生命周期

Spring Framework的主要功能是用来存储和读取Bean,Bean于Spring框架的地位可见一斑。本节的目的就是从作用域和生命周期的角度更加深入了解一下Bean对象。

1. Bean的作用域

首先我们来学习一下Bean的作用域,源码位置:bean_scope

1.1 作用域的定义

作用域一词在学习JavaSE的时候就有接触过,指的是源代码中定义变量的某个区域,与JavaSE中定义的作用域不同,Bean 的作用域是指Bean在Spring整个框架中的某种行为模式,比如Spring中默认的Ben作用域:singleton单例作用域,指的就是在整个Spring中只有一份。

Spring有6种作用域:

  1. singleton: 单例作用域
  2. prototype: 原型作用域(多例)
  3. request: 请求作用域
  4. session: 会话作用域
  5. application: 全局作用域
  6. websocket: HTTP WebSocket 作用域

后 4 种状态是Spring MVC 中的值, 在普通的 Spring 项目中只有前两种

【问题】默认作用域的不足

Spring中Bean的默认作用域是singleton单例作用域,在业务中可能遇到下面这种场景:

首先在名为model的包中定义User,用来传输User的数据:

public class User {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + ''' +
                ", age=" + age +
                '}';
    }
}

创建一个bean包,并定义一个UserBean,用于初始化User并将User存入Spring容器中:

@Component
public class UserBean {

    @Bean(name = "user")
    public User getUserBean() {
        User user = new User();
        user.setName("zhangsan");
        user.setAge(20);
        return user;
    }
}

controller包中创建UserController1UserController2分别代表两个业务

程序员A在业务中需要临时创建临时的User对象并且需要进行修改,出于无心之举,创建的临时对象user2直接浅拷贝了Bean,于是修改了Bean的数据,然后将代码推送到仓库:

@Controller
public class UserController1 {
    @Autowired
    private User user;

    public void doController() {
        System.out.println("Do UserController1");
        System.out.println("从Spring中取出user:" + user);
        //创建的临时对象user2直接浅拷贝了Bean
        User user2 = user;
        user2.setName("lisi");
        user2.setAge(18);
        System.out.println("修改后的数据" + user2);
    }
}

程序员B拉取了新的代码,并且要将 UserController2 给其他业务部门交付,并展示,预期中User的数据是User{name='zhangsan', age=20}

@Controller
public class UserController2 {
    @Autowired
    private User user;

    public void  doController() {
        System.out.println("Do UserController2");
        System.out.println("从Spring中取出user:" + user);
    }
}

程序员B展示他的业务代码并一顿鼓吹他是如何拿到这么一个User{name='zhangsan', age=20}的数据:

public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("bean-scope.xml");
    //UserController1业务
    UserController1 userController1 = context.getBean("userController1", UserController1.class);
    userController1.doController();
    System.out.println("===========================");
    //UserController2业务
    UserController2 userController2 = context.getBean("userController2", UserController2.class);
    userController2.doController();
}

但是他并不知道代码被修改,在展示的过程中直接傻眼了,运行结果如下:

Do UserController1
从Spring中取出user:User{name='zhangsan', age=20}
修改后的数据User{name='lisi', age=18}
===========================
Do UserController2
从Spring中取出user:User{name='lisi', age=18}

1.2 Bean的 6 种作用域

上述问题的解决方案就是设置 Bean 的作用域,在说明设置 Bean 的作用域的方法前,先来介绍下 Bean 的 6 种作用域的定义。

1) singleton作用域(单例)

官方说明:(Default)Scopes a single bean definition to a single object instance for each Spring IoC container.

描述: 该作用域下的Bean在IoC容器中只存在一个实例:singleton作用域是Spring中Bean的默认作用域,singleton作用域指的是单例作用域,这里的单例和常规设计模式中的单例又稍微一些不一样,常规设计模式中的单例模式指的是一个类只能对应唯一的实例对象;而 Bean 的单例作用域表示的是 Bean 对象在Spring中只有一份,它是全局共享的

场景: 通常无状态的Bean使用该作用域,无状态表示Bean的属性不需要更新。

2) prototype作用域 (原型)

官方说明: Scopes a single bean definition to any number of object instances.

描述: 每次该作用域下的获取Bean的请求都会创建新的实例:这种方式对比singleton作用域,会频繁创建实例,因此开销较大。

场景: 通常有状态的Bean使用该作用域,如前面所提到的例子,就可以用到该作用域。

3) request作用域

官方说明: Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTPrequest has its own instance of a bean created off the back of a single bean definition. Only valid in thecontext of a web-aware Spring ApplicationContext.

描述: 每次HTTP请求会创建新的Bean实例,它的生命周期和一个HTTP的生命周期相同。

场景: 一次HTTP的请求和响应的共享Bean,如在处理一个HTTP请求的过程中,可能有多个组件或步骤需要访问或修改同一份数据,此时就可以用上request作用域。

备注: 限定Spring MVC中使用

4) session作用域

官方说明: Scopes a single bean definition to the lifecycle of an HTTP Session. Only a web-aware Spring ApplicationContext.

描述: 在一个http session(会话)中,定义一个Bean实例,它的生命周期和会话的生命周期相同。

场景: 用户会话的共享Bean,常用于在用户的整个会话过程中共享数据,比如:记录一个用户的登录信息。

备注: 限定Spring MVC中使用

5) application作用域

官方说明: Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of
a web-aware Spring ApplicationContext.

描述: 一个http servlet context共享的Bean,它们在服务器开始执行服务时创建,并持续存在直到服务器关闭。

场景: Web应用的上下文信息,比如:记录一个应用的共享信息

备注: 限定Spring MVC中使用

6) websocket作用域

官方说明: Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of web-aware Spring ApplicationContext

描述: 在一个HTTP WebSocket的生命周期中,定义一个Bean实例

对WebSocket的解释: HTTP协议是基于请求/响应模式的,在客户端发起请求,服务端返回响应后,连接就结束了。WebSocket可以看作是可以实现长连接的HTTP的升级,弥补了HTTP协议无法进行长连接的缺点,一次WebSocket的连接可以看作是Websocket会话。

场景: WebSocket的每次会话中,开始时初始化Bean,直到WebSocket结束都是同一个Bean。

备注: 限定Spring MVC中 使用

1.3 【方案】设置作用域

使用@Scope标签就可以用来声明 Bean 的作用域,如下面代码:

@Component
public class UserBean {

    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Bean(name = "user")
    public User getUserBean() {
        User user = new User();
        user.setName("zhangsan");
        user.setAge(20);
        return user;
    }
}

@Scope标签既可以修饰方法也可以修饰类,它有两种设置方式:

  1. 直接设置值:@Scope("prototype")
  2. 使用 ConfigurableBeanFactory(推荐):@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

其实SCOPE_PROTOTYPE就是 ConfigurableBeanFactory 的一个字段:
image.png

在设置了prototype作用域后,终于能够如预期拿到结果:

Do UserController1
从Spring中取出user:User{name='zhangsan', age=20}
修改后的数据User{name='lisi', age=18}
===========================
Do UserController2
从Spring中取出user:User{name='zhangsan', age=20}

2 Spring主要执行流程

2.1 容器初始化

ApplicationContext context = 
        new ClassPathXmlApplicationContext("bean-lifecycle.xml");

2.2 Bean的实例化

读取xml配置文件中配置的所有 Bean 对象以及xml配置文件所配置的扫描路径下添加六大注解的Bean,通过反射实例化Bean(包括分配内存空间和调用构造方法)

<!--  xml中直接配置的Bean  -->
<bean id="userXml" class="com.chenshu.lifecycle.model.UserXml"/>
<!--  xml中配置的扫描路径  -->
<content:component-scan base-package="com.chenshu.lifecycle"/>

2.3 Bean的依赖注入

在依赖注入前,对象仍然是一个原生的状态,并没有进行依赖注入,值得一提的依赖注入这一步并不一定是在2. Bean的实例+初始化之后,如果是构造方法注入的话,会在2. Bean的实例+初始化的初始化的过程中注入所需依赖。

扩展:如果Bean所需要依赖注入的对象没有完全初始化好,会先去完全初始化好(也就是依赖注入)该对象,再注入该Bean

2.4 存入Spring容器中

Spring容器会将这个已经完全初始化好的Bean存入IoC容器中。IoC容器通常是一个Map结构的实现,它使用Bean的名称或ID作为键,Bean的实例作为值。

2.5 使用Bean

应用程序可以通过依赖注入或依赖查找机制来获取bean的引用,并调用其方法。

2.6 销毁Spring容器

Spring容器关闭时,会触发bean的销毁过程。

3. Bean的生命周期

源码位置:bean-lifecycle

Bean的生命周期分为以下5大部分:

  1. 实例化 Bean(为Bean分配内存空间)
  2. 设置属性(Bean的依赖注入,如@Autowired
  3. Bean 初始化
    • 执行各种通知(xxxAware的接口方法)
    • 执行初始化方法(各种初始化方法执行顺序如下)
      • @PostConstruct定义的初始化方法
      • 判断是否为InitializingBean(调用afterPropertiesSet)
      • xml中定义的init-method方法
  4. 使用Bean
  5. 销毁Bean
    • 销毁Bean前的各种方法,如@PreDestroyDisposableBean接口方法,destroy-method.

Untitled Diagram.drawio-2.png

3.1 代码验证

User类,为了演示依赖注入的时机:

@Component
public class User implements BeanPostProcessor {
}

BeanLifeComponent类,演示了生命周期:

@Component
public class BeanLifeComponent implements BeanNameAware, InitializingBean {
    private User user;
    //构造方法
    public BeanLifeComponent() {
        System.out.println("执行构造方法");
    }
    //setter依赖注入
    @Autowired
    public void setUser(User user) {
        this.user = user;
        System.out.println("依赖注入");
    }
    //BeanNameAware的通知方法
    @Override
    public void setBeanName(String name) {
        System.out.println("执行了BeanName通知方法,name="+name);
    }
    //初始化方法
    @PostConstruct
    public void initByAnnotation() {
        System.out.println("执行@PostConstrut修饰的init方法");
    }
    //初始化方法
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("执行InitializingBean接口的afterPropertiesSet方法");
    }
    //初始化方法
    public void initByXml() {
        System.out.println("执行xml中定义的init方法");
    }
    //使用Bean
    public void use() {
        System.out.println("使用use方法");
    }
    //销毁前调用的方法
    @PreDestroy
    public void destroy() {
        System.out.println("执行@PreDestroy修饰的destroy方法");
    }
}

xml中的配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:content="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="beanLifeComponent" 
          class="com.chenshu.lifecycle.beanlife.BeanLifeComponent"
          init-method="initByXml"/>

    <content:component-scan base-package="com.chenshu.lifecycle"/>
</beans>

运行结果:

执行构造方法
依赖注入
执行了BeanName通知方法,name=beanLifeComponent
执行@PostConstrut修饰的init方法
执行InitializingBean接口的afterPropertiesSet方法
执行xml中定义的init方法
使用use方法
执行@PreDestroy修饰的destroy方法

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

iMazing如何备份手机资料 iPhone的资料可以传到iPad里吗 iphone备份到mac 苹果导入备份

在当今信息化快速发展的时代&#xff0c;手机已经成为我们生活中不可或缺的一部分。随着资料的积累&#xff0c;备份手机数据成了一个重要的问题。本文将介绍iMazing如何备份手机资料&#xff0c;并为大家解答“iPhone的资料可以传到iPad里吗”这一问题。这不仅可以帮助你有效管…

mybaits(8)-缓存机制

缓存机制 1、mybatis缓存2、一级缓存2.1 开启一级缓存2.2 一级缓存失效 3、二级缓存3.1 开启二级缓存3.2 二级缓存什么时候失效3.3 二级缓存的相关配置 4、MyBatis集成EhCache 1、mybatis缓存 缓存&#xff1a;cache 缓存的作用&#xff1a;通过减少IO的方式&#xff0c;来提高…

最齐全,最简单的免费SSL证书获取方法——实现HTTPS访问

一&#xff1a;阿里云 优势&#xff1a;大平台&#xff0c;在站长中知名度最高&#xff0c;提供20张免费单域名SSL证书 缺点&#xff1a;数量有限&#xff0c;并且只有单域名证书&#xff0c;通配符以及多域名没有免费版本。并且提供的单域名证书只有三个月的期限。 二&#…

v1.9.2-httpsok快速申请免费SSL证书

v1.9.2-&#x1f525;httpsok快速申请免费SSL证书 介绍 httpsok 是一个便捷的 HTTPS 证书自动续签工具&#xff0c;专为 Nginx 、OpenResty 服务器设计。已服务众多中小企业&#xff0c;稳定、安全、可靠。 一行命令&#xff0c;一分钟轻松搞定SSL证书自动续期 更新日志 V1…

【vue】购物车案例优化

对 购物车案例 进行优化 用watch实现全选/取消全选用watch实现全选状态的检查用computed计算总价格 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-w…

Zookeeper的集群搭建和ZAB协议详解

Zookeeper的集群搭建 1&#xff09;zk集群中的角色 Zookeeper集群中的节点有三个角色&#xff1a; Leader&#xff1a;处理集群的所有事务请求&#xff0c;集群中只有一个LeaderFollwoer&#xff1a;只能处理读请求&#xff0c;参与Leader选举Observer&#xff1a;只能处理读…

HashMap部分底层源码解析

哈希表的物理结构 HashMap底层都是哈希表&#xff08;也称散列表&#xff09;&#xff0c;线程不安全&#xff0c;其中维护了一个长度为2的幂次方的Entry类型的数组table&#xff0c;数组的每一个索引位置被称为一个桶(bucket)&#xff0c;你添加的映射关系(key,value)最终都被…

clickhouse深入浅出

基础知识原理 极致压缩率 极速查询性能 列式数据库管理 &#xff0c;读请求多 大批次更新或无更新 读很多但用很少 大量的列 列的值小数值/短字符串 一致性要求低 DBMS&#xff1a;动态创建/修改/删除库 表 视图&#xff0c;动态查/增/修/删&#xff0c;用户粒度设库…

Go——Goroutine介绍

一. 并发介绍 进程和线程 进程是程序在操作系统中一次执行过程&#xff0c;系统进程资源分配和调度的一个独立单位。线程是进程执行的实体&#xff0c;是CPU调度和分派的基本单位&#xff0c;它是比进程更小的能独立运行的基本单位。一个进程可以创建和撤销多个线程&#xff0c…

sysbench MySQL性能测试

目录 1. QPS&&TPS 1.1 数据库启动到现在的运行时间(秒) 1.2 查询量 1.3 status命令直接显示出QPS 1.4 每秒输出数据库状态(累加) 2. sysbench 测试工具 3. OLTP MySQL测试 3.1 普通参数 3.2 支持的lua脚本 3.3 脚本参数 3.4 测试数据准备 3.5 进行测试 3.…

硬件18、PCB中元器件旋转

器件旋转45度&#xff0c;如果只是用空格的话&#xff0c;器件只能旋转90度 不要用x和y&#xff0c;因为那是器件的镜像&#xff0c;但是实际器件没有镜像&#xff0c;就会导致焊接失败的问题

FPGA - 以太网UDP通信(三)

一&#xff0c;引言 前文链接&#xff1a;FPGA - 以太网UDP通信&#xff08;一&#xff09; FPGA - 以太网UDP通信&#xff08;二&#xff09; 在以上文章中介绍了以太网简介&#xff0c;以太网UDP通信硬件结构&#xff0c;以及PHY芯片RGMII接口-GMII接口转换逻辑&#xff0c…

云架构(四)异步请求-应答模式

Asynchronous Request-Reply pattern - Azure Architecture Center | Microsoft Learn 把后台处理和前端解耦&#xff0c;后台处理需要异步处理&#xff0c;但是也需要给前端一个清晰的回应。 背景和问题 在现代应用开发中&#xff0c;代码通常在浏览器中运行&#xff0c;依…

HarmonyOS实战开发-录音机、如何实现音频录制和播放的功能

介绍 本示例使用audio相关接口实现音频录制和播放的功能&#xff0c;使用mediaLibrary实现音频文件的管理。 相关概念&#xff1a; AudioRecorder&#xff1a;音频录制的主要工作是捕获音频信号&#xff0c;完成音频编码并保存到文件中&#xff0c;帮助开发者轻松实现音频录…

麒麟 V10 离线 安装 k8s 和kuboard

目录 安装文件准备 主机准备 主机配置 修改主机名&#xff08;三个节点分别执行&#xff09; 配置hosts&#xff08;所有节点&#xff09; 关闭防火墙、selinux、swap、dnsmasq(所有节点) 安装依赖包&#xff08;所有节点&#xff09; 系统参数设置(所有节点) 时间同步…

【Qt】界面优化

目录 一、QSS 1.1 基本语法 1.2 QSS设置方法 1.2.1 指定控件样式设置 1.2.2 全局样式设置 1.2.3 从文件加载样式表 1.2.4 使用Qt Designer编辑样式 1.3 选择器 1.3.1 介绍 1.3.2 子控件选择器 1.3.3 伪类选择器 1.4 样式属性(盒模型) 1.5 代码示例(登录界面) 二、…

html中的“居中”问题详解(超全)

html中的“居中”问题详解&#xff08;超全&#xff09; 图片居中文本居中定位居中元素居中响应式设计中的居中技巧 引言&#xff1a; 在网页设计和开发中&#xff0c;实现元素的居中是一个常见但也常被低估的挑战。无论是在传统的网页布局中还是在响应式设计中&#xff0c;居中…

DP10RF001一款工作于200MHz~960MHz低功耗、高性能、单片集成的(G)FSK/OOK无线收发芯片

产品概述. DP10RF001是一款工作于200MHz~960MHz范围内的低功耗、高性能、单片集成的(G)FSK/OOK无线收发机芯片。内部集成完整的射频接收机、射频发射机、频率综合器、调制解调器&#xff0c;只需配备简单、低成本的外围器件就可以获得良好的收发性能。芯片支持灵活可设的数据包…

MySQL之sql性能分析

sql执行频率 MySQL客户端连接成功后&#xff0c;通过show[session|global]status命令可以提供服务器状态信息。通过如下指令&#xff0c;可以查看当前数据库的所有INSERT、DELETE、UPDATE、SELECT的访问频次。 慢日志查询 慢查询日志记录了所有执行时间超过指定参数(longquer…

AR地图导览小程序是怎么开发出来的?

在移动互联网时代&#xff0c;AR技术的发展为地图导览提供了全新的可能性。AR地图导览小程序结合了虚拟现实技术和地图导航功能&#xff0c;为用户提供了更加沉浸式、直观的导览体验。本文将从专业性和思考深度两个方面&#xff0c;探讨AR地图导览小程序的开发方案。 编辑搜图 …