Spring | Bean 作用域和生命周期

一、通过一个案例来看 Bean 作用域的问题

Spring 是用来读取和存储 Bean,因此在 Spring 中 Bean 是最核心的操作资源,所以接下来我们深入学习⼀下 Bean 对象

假设现在有⼀个公共的 Bean,提供给 A 用户和 B 用户使用,然而在使用的途中 A 用户却 “悄悄” 地修改了公共 Bean 的数据,导致 B 用户在使用时发生了预期之外的逻辑错误

我们预期的结果是,公共 Bean 可以在各自的类中被修改,但不能影响到其他类

1、被修改的 Bean 案例

——公共 Bean:

@Component
public class Users {
	@Bean
	public User user1() {
		User user = new User();
		user.setId(1);
		user.setName("Java"); // 【重点:名称是 Java】
		return user;
	}
}

——A 用户使用时,进行了修改操作:

@Controller
public class BeanScopesController {
	@Autowired
	private User user1;

    public User getUser1() {
		User user = user1;
		System.out.println("Bean 原 Name:" + user.getName());
		user.setName("悟空"); // 【重点:进行了修改操作】
		return user;
	}
}

——B 用户再去使用公共 Bean 的时候:

@Controller
public class BeanScopesController2 {
	@Autowired
	private User user1;

    public User getUser1() {
		User user = user1;
		return user;
	}
}

——打印 A 用户和 B 用户公共 Bean 的值:

public class BeanScopesTest {
	public static void main(String[] args) {
		ApplicationContext context = new 
            ClassPathXmlApplicationContext("spring-config.xml");
		
        BeanScopesController beanScopesController = context.getBean(BeanScopesController.class);
		System.out.println("A 对象修改之后 Name:" + 
                           beanScopesController.getUser1().toString());
		
        BeanScopesController2 beanScopesController2 = context.getBean(BeanScopesController2.class);
		System.out.println("B 对象读取到的 Name:" + 
                           beanScopesController2.getUser1().toString());
	}
}

——执行结果如下:

Bean 原 Name: Java (原来的值)
A 对象修改之后 Name: 1:悟空 (被 A 对象修改)
B 对象读取到的 Name: 1:悟空 (B 对象中也跟着被更新了)


2、原因分析

操作以上问题的原因是因为 Bean 默认情况下是单例状态(singleton),也就是所有⼈的使用的都是同⼀个对象,之前我们学单例模式的时候都知道,使用单例可以很⼤程度上提高性能,所以在 Spring 中Bean 的作用域默认也是 singleton 单例模式


二、作用域定义 Scope

限定程序中变量的可用范围叫做作用域,或者说在源代码中定义变量的某个区域就叫做作用域

而 Bean 的作用域是指 Bean 在 Spring 整个框架中的某种行为模式,⽐如 singleton 单例作用域,就表示 Bean 在整个 Spring 中只有⼀份,它是全局共享的,那么当其他⼈修改了这个值之后,那么另⼀个⼈读取到的就是被修改的值


1、Bean 的 6 种作用域

Spring 容器在初始化⼀个 Bean 的实例时,同时会指定该实例的作用域。Spring有 6 种作用域,最后四种是基于 Spring MVC 生效的:

  1. singleton :单例作用域(默认)

  2. prototype :原型作用域(多例作用域)

  3. request :请求作用域(Spring MVC)

  4. session :回话作用域(Spring MVC)

  5. application :全局作用域(Spring MVC)

  6. websocket :HTTP WebSocket 作用域(Spring WebSocket)

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


2、设置 bean 作用域

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

  • 直接设置值:@Scope("prototype")
  • 类似枚举常量的设置:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

2.1、测试 Bean 默认是那种作用域

——UserBeans:

@Component
public class UserBeans {
    @Bean(name = "user1")
    public User getUser1() {
        User user = new User();
        user.setId(1);
        user.setName("zhangsan");
        return user;
    }

    @Bean(name = "user2")
    public User getUser2() {
        User user = new User();
        user.setId(2);
        user.setName("lisi");
        return user;
    }

    @Bean(name = "user3")
    public User getUser3() {
        User user = new User();
        user.setId(3);
        user.setName("Java");
        return user;
    }
}

——创建类 BeanScope1:

@Component
public class BeanScope1 {
    @Autowired // 注入 user3
    private User user3;

    public User getUser3() {
        User user = user3;
        user.setName("八戒");
        return user;
    }
}

——创建类 BeanScope2:

@Component
public class BeanScope2 {
    @Autowired
    private User user3;

    public User getUser3() {
        return user3;
    }
}

——测试:

public class App {
    public static void main(String[] args) {
        ApplicationContext context = new
                ClassPathXmlApplicationContext("spring-config.xml");
        BeanScope1 beanScope1 = context.getBean(BeanScope1.class);
        User user1 = beanScope1.getUser3();
        System.out.println("BeanScope1" + user1);

        BeanScope2 beanScope2 = context.getBean(BeanScope2.class);
        User user2 = beanScope2.getUser3();
        System.out.println("BeanScope2" + user2);
    }
}

运行结果: 说明默认为单例

BeanScope1User{id=3, name=‘八戒’}
BeanScope2User{id=3, name=‘八戒’}


2.2、设置作用域

  • @Scope(“prototype”)
@Bean(name = "user3")
@Scope("prototype") // 原型模式,每次请求生成一个对象
public User getUser3() {
    User user = new User();
    user.setId(3);
    user.setName("Java");
    return user;
}

运行结果:

BeanScope1User{id=3, name=‘八戒’}
BeanScope2User{id=3, name=‘Java’}

  • @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Bean(name = "user3")
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public User getUser3() {
        User user = new User();
        user.setId(3);
        user.setName("Java");
        return user;
    }

在这里插入图片描述

运行结果:

BeanScope1User{id=3, name=‘八戒’}
BeanScope2User{id=3, name=‘Java’}


2.3、singleton

官方说明:(Default) Scopes a single bean definition to a single object instance for each
Spring IoC container.
描述:该作⽤域下的Bean在IoC容器中只存在⼀个实例:获取Bean(即通过
applicationContext.getBean等方法获取)及装配Bean(即通过@Autowired注⼊)都是同⼀
个对象。
场景:通常无状态的Bean使⽤该作⽤域。无状态表示Bean对象的属性状态不需要更新
备注:Spring默认选择该作⽤域


2.4、prototype

官方说明:Scopes a single bean definition to any number of object instances.
描述:每次对该作⽤域下的Bean的请求都会创建新的实例:获取Bean(即通过
applicationContext.getBean等方法获取)及装配Bean(即通过@Autowired注⼊)都是新的
对象实例。
场景:通常有状态的Bean使⽤该作⽤域


2.5、request

官方说明:Scopes a single bean definition to the lifecycle of a single HTTP request. That
is, each HTTP request has its own instance of a bean created off the back of a single
bean definition. Only valid in the context of a web-aware Spring ApplicationContext.
描述:每次http请求会创建新的Bean实例,类似于prototype
场景:⼀次http的请求和响应的共享Bean
备注:限定SpringMVC中使⽤


2.6、session

官方说明:Scopes a single bean definition to the lifecycle of an HTTP Session. Only
valid in the context of a web-aware Spring ApplicationContext.
描述:在⼀个http session中,定义⼀个Bean实例
场景:⽤户回话的共享Bean, ⽐如:记录⼀个⽤户的登陆信息
备注:限定SpringMVC中使⽤


2.7、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应⽤的上下⽂信息,⽐如:记录⼀个应⽤的共享信息
备注:限定SpringMVC中使⽤


2.8、websocket(了解)

官方说明:Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in
the context of a web-aware Spring ApplicationContext.
描述:在⼀个HTTP WebSocket的生命周期中,定义⼀个Bean实例
场景:WebSocket的每次会话中,保存了⼀个Map结构的头信息,将⽤来包裹客户端消息
头。第⼀次初始化后,直到WebSocket结束都是同⼀个Bean。
备注:限定Spring WebSocket中使⽤


2.9、单例作⽤域(singleton) 和 全局作⽤域(application) 的区别

项目类型不同: singleton 是 Spring Core 的作⽤域;application 是 Spring Web 中的作⽤域;
作用容器不同: singleton 作⽤于 IoC 的容器,而 application 作⽤于 Servlet 容器


3、Bean 原理分析

Bean 执行流程(Spring 执行流程):启动 Spring 容器 -> 实例化 Bean(分配内存空间,从无到有)
-> Bean 注册到 Spring 中(存操作) -> 将 Bean 装配到需要的类中(取操作)

在这里插入图片描述


三、Bean 生命周期

1、5 个流程

所谓的生命周期指的是⼀个对象从诞生到销毁的整个生命过程,我们把这个过程就叫做⼀个对象的生命周期。

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

  1. 实例化 Bean(为 Bean 分配内存空间)

  2. 设置属性(Bean 注⼊和装配)

  3. Bean 初始化

    • 实现了各种 Aware 通知的方法,如 BeanNameAware、BeanFactoryAware、ApplicationContextAware 的接口方法;

    • 执行 BeanPostProcessor 初始化前置方法;

    • 执行构造方法,有两种执行方式:

    • 注解: @PostConstruct 初始化方法,依赖注⼊操作之后被执行;

    • xml:执行自己指定的 init-method 方法(如果有指定的话);<bean init-method=""></bean>

    • 如果两个都设置了,先执行注解,

    • 执行 BeanPostProcessor 初始化后置方法

  4. 使⽤ Bean

  5. 销毁 Bean

    • 销毁容器的各种方法,如 @PreDestroy (注解)、DisposableBean 接口方法、destroy-method (xml)

2、实例化和初始化的区别

实例化和属性设置是 Java 级别的系统“事件”,其操作过程不可⼈⼯⼲预和修改;而初始化是给
开发者提供的,可以在实例化之后,类加载完成之前进行⾃定义“事件”处理


3、生命流程的“故事”

Bean 的生命流程看似繁琐,但咱们可以以生活中的场景来理解它,比如我们现在需要买一栋房子,那么我们的流程是这样的:

  1. 先买房(实例化,从无到有);

  2. 装修(设置属性);

  3. 买家电,如洗衣机、冰箱、电视、空调等([各种]初始化);

  4. 入住(使用 Bean);

  5. 卖出去(Bean 销毁)


4、生命周期演示

——创建类 BeanLifeComponent:

// @Component // 在 xml 中 使用 bean 标签注入了,不需要类注解
public class BeanLifeComponent implements BeanNameAware {
    @PostConstruct
    public void postConstruct() {
        System.out.println("执⾏ @PostConstruct()");
    }

    public void init() {
        System.out.println("执⾏ init-method");
    }
    
    public void use() {
        System.out.println("使用 bean");
    }
    
    @PreDestroy
    public void preDestroy() {
        System.out.println("执⾏ @PreDestroy");
    }

    public void setBeanName(String s) {
        System.out.println("执⾏了 Aware 通知");
    }
}

——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">
    <content:component-scan base-package="com.beans"></content:component-scan>

    <bean id="beanLifeComponent" class="com.beans.BeanLifeComponent" init-method="init"></bean>

</beans>

——调⽤类:

public class App {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        BeanLifeComponent beanLifeComponent = 
            context.getBean("beanLifeComponent", BeanLifeComponent.class);
        beanLifeComponent.use();
        context.destroy(); // 注销上下文对象
    }
}

运行结果:

执行了 Aware 通知
执行 @PostConstruct()
执行 init-method
使用 bean
执行 @PreDestroy


2、为什么先设置属性再初始化

也就是 步骤 2 和 步骤 3 的顺序,能不能打个颠倒?

@Service
public class UserService {
    public UserService(){
        System.out.println("调⽤ User Service 构造⽅法");
    }
    public void sayHi(){
        System.out.println("User Service SayHi.");
    }
}

@Controller
public class UserController {
    @Resource
    private UserService userService;

    @PostConstruct
    public void postConstruct() {
        userService.sayHi(); // 执行构造方法时使用 会空指针
        System.out.println("执⾏ User Controller 构造⽅法");
    }
}

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

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

相关文章

【嵌入式学习笔记】嵌入式入门2——中断(外部中断)

1.什么是中断 打断CPU执行正常的程序&#xff0c;转而处理紧急程序&#xff0c;然后返回原暂停的程序继续运行&#xff0c;就叫中断 1.1.中断的作用与意义 作用1&#xff1a;实时控制在确定时间内对相应事件作出响应——定时器中断作用2&#xff1a;故障处理检测到故障&…

基于linux下的高并发服务器开发(第四章)- 多进程实现并发服务器(回射服务器)

1. socket // 套接字通信分两部分&#xff1a; - 服务器端&#xff1a;被动接受连接&#xff0c;一般不会主动发起连接 - 客户端&#xff1a;主动向服务器发起连接 2.字节序转换函数 当格式化的数据在两台使用不同字节序的主机之间直接传递时&#xff0c;接收端必然错误…

无涯教程-jQuery - Spinner组件函数

Widget Spinner 函数可与JqueryUI中的窗口小部件一起使用。Spinner提供了一种从一组中选择一个值的快速方法。 Spinner - 语法 $( "#menu" ).selectmenu(); Spinner - 示例 以下是显示Spinner用法的简单示例- <!doctype html> <html lang"en"…

CentOS 项目发出一篇奇怪的博文

导读最近&#xff0c;在红帽限制其 RHEL 源代码的访问之后&#xff0c;整个社区围绕这件事发生了很多事情。 CentOS 项目发出一篇奇怪的博文 周五&#xff0c;CentOS 项目董事会发出了一篇模糊不清的简短博文&#xff0c;文中称&#xff0c;“发展社区并让人们更容易做出贡献…

代码随想录算法训练营第五天| 242. 有效的字母异位词,349. 两个数组的交集,202快乐数,1. 两数之和

哈希表 首先什么是 哈希表&#xff0c;哈希表&#xff08;英文名字为Hash table&#xff0c;国内也有一些算法书籍翻译为散列表&#xff0c;大家看到这两个名称知道都是指hash table就可以了&#xff09;。 那么哈希表能解决什么问题呢&#xff0c;一般哈希表都是用来快速判断…

29.利用fminbnd 求解 最大容积问题(matlab程序)

1.简述 用于求某个给定函数的最小值点。 使用方法是&#xff1a; xfminbnd(func,x1,x2) func是函数句柄&#xff0c;然后x1和x2就是函数的区间&#xff0c;得到的结果就是使func取最小值的x值 当然也可以使用[x,fv]fminbnd(func,x1,x2)的方式&#xff0c;这个时候fv就是函数…

Web3.0:已经开启的互联网革命!

1 痛点 2 web发展形态 只读、封闭式、协作式。 3 一个高度联系、全球统一的数字经济体 去中心化架构通过计算几余打破数据垄断&#xff0c;同时实现数字确权大量的功能依靠智能合约自动实现&#xff0c;运转效率大大提升DAO大量涌现&#xff0c;全球范围实现资源配置 4 特…

MAC电脑设置charles,连接手机的步骤说明(个人实际操作)

目录 一、charles web端设置 1. 安装charles之后&#xff0c;先安装证书 2. 设置 Proxy-Proxy Settings 3. 设置 SSL Proxying 二、手机的设置 1. 安卓 2. ios 资料获取方法 一、charles web端设置 1. 安装charles之后&#xff0c;先安装证书 Help-SSL Proxying-Inst…

我的第一个前端(VS code ,Node , lite-server简易服务器,npm 运行)

第一种方式&#xff1a;使用Visual Studio Code创建并运行 第一个前端项目的步骤&#xff0c;如下&#xff1a; 1. 下载和安装Visual Studio Code&#xff1a; 访问Visual Studio Code官方网站&#xff08;Visual Studio Code - Code Editing. Redefined&#xff09;并根据你…

基于x-scan扫描线的3D模型渲染算法

基于x-scan算法实现的z-buffer染色。c#语言&#xff0c;.net core framework 3.1运行。 模型是读取3D Max的obj模型。 x-scan算法实现&#xff1a; public List<Vertex3> xscan() {List<Vertex3> results new List<Vertex3>();SurfaceFormula formula g…

算法通关村第三关——双指针的妙用

双指针思想 快慢指针 所谓的双指针其实就是两个变量。双指针思想简单好用&#xff0c;在处理数组、字符串等场景下很常见。看个例子&#xff0c;从下面序列中删除重复元素[1,2,2,2,3,3,3,5,5,7,8]&#xff0c;重复元素只保留一个。删除之后的结果应该为[1,2,3,5,7,8]。我们可以…

应用程序接口(API)安全的入门指南

本文简单回顾了 API 的发展历史&#xff0c;其基本概念、功能、相关协议、以及使用场景&#xff0c;重点讨论了与之相关的不同安全要素、威胁、认证方法、以及十二项优秀实践。 根据有记录的历史&#xff0c;随着 Salesforce 的销售自动化解决方案的推出&#xff0c;首个 Web A…

HCIP期中实验

考试需求 1 、该拓扑为公司网络&#xff0c;其中包括公司总部、公司分部以及公司骨干网&#xff0c;不包含运营商公网部分。 2 、设备名称均使用拓扑上名称改名&#xff0c;并且区分大小写。 3 、整张拓扑均使用私网地址进行配置。 4 、整张网络中&#xff0c;运行 OSPF 协议…

PostgreSQL中如何配置Huge page的数量

在了解如在PG中如何配置大页之前&#xff0c;我们先要对大页进行一定的了解&#xff0c;为什么要配置大页&#xff0c;配置大页的好处有哪些。 我们日常的操作系统中&#xff0c;程序不直接使用内存&#xff0c;而是使用虚拟内存地址来处理内存分配&#xff0c;避免计算的复杂…

1.3 eureka+ribbon,完成服务注册与调用,负载均衡源码追踪

本篇继先前发布的1.2 eureka注册中心&#xff0c;完成服务注册的内容。 目录 环境搭建 采用eurekaribbon的方式&#xff0c;对多个user服务发送请求&#xff0c;并实现负载均衡 负载均衡原理 负载均衡源码追踪 负载均衡策略 如何选择负载均衡策略&#xff1f; 饥饿加载…

windows下tomcat无故宕机,检测http或https服务,并自动重启Tomcat服务

一、问题描述及解决原理 把项目发布到windows服务器中&#xff0c;如tomcat工程不稳定&#xff0c;会有无故宕机的问题。如果通过程序无法解决&#xff0c;并且重启tomcat服务能够生效的话&#xff0c;可以做一个自动检测并重启的脚本。 脚本通过检测tomcat对应的工程链接&…

一文了解Angular、React 和 Vue.js的区别

前端开发人员在开始一个新项目时首先要回答的问题是&#xff1a;我应该选择哪个框架&#xff1f; 哪个框架更适合我的需求&#xff1f; 在本文中&#xff0c;我们将向您快速概述当前使用的最常见的前端框架&#xff0c;旨在帮助您选择最能满足您需求的框架。这些框架是 Angular…

【雕爷学编程】Arduino动手做(177)---ESP-32 掌控板

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

PHP8的常量-PHP8知识详解

常量和变量是构成PHP程序的基础&#xff0c;在PHP8中常量的这一节中&#xff0c;主要讲到了定义常量和预定义常量两大知识点。 一、定义常量 定义常量也叫声明常量。在PHP8中&#xff0c;常量就是一个标识符&#xff08;名字&#xff09;&#xff0c;一旦定义&#xff08;声明&…

Java常用API:Math、Syetem、Runtime、BigDecimal

Math类 //目标:了解下Nath类提供的常见方法。 // 1、public static int abs(int a):取绝对值&#xff08;拿到的结果一定是正数&#xff09; //public static double abs(double a) system.out.println(Math.abs(-12)); // 12 system.out.println(Math.abs(123));// 123 system…
最新文章