Bean的作用域和生命周期

🔎前置


引入 Lombok

  1. 引入 Lombok 相关依赖
  2. 下载 Lombok 插件

利用 Lombok 的注解可自动添加所需的方法

例如

  • @Getter 自动添加 getter 方法
  • @Setter 自动添加 setter 方法
  • @ToString 自动添加 toString 方法
  • @Data 包含上述的所有方法

在这里插入图片描述

引入 Lombok 相关依赖


复制如下代码至pom.xml

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
     <groupId>org.projectlombok</groupId>
     <artifactId>lombok</artifactId>
     <version>1.18.24</version>
     <scope>provided</scope>
 </dependency>

在这里插入图片描述

下载 Lombok 插件


如果未找到, 可能的原因是已经下载好了(点击 Installed 查看是否下载)

在这里插入图片描述

🔎Bean的6种作用域


什么是作用域?

举个栗子🌰

定义一个成员变量, 该变量的调用范围是整个类(该成员变量的作用域)
定义一个局部变量. 该变量的调用范围是在定义该局部变量的方法内(该局部变量的作用域)

Spring 容器在初始化 Bean 实例时, 同时会指定该实例的作用域

  • singleton, 单例作用域
  • prototype, 原型作用域
  • request, 请求作用域
  • session, 会话作用域
  • application, 全局作用域
  • websocket, HTTP WebSocket 作用域

(前4种较为常见)

Bean 的作用域是指 Bean 在整个 Spring 框架中的某种行为模式
(例如 singleton — 单例作用域, 表示 Bean 在整个 Spring 中只有一份, 是全局共享的, 类似于成员变量)

对Bean作用域的解释


定义 User 类

包含 2 个属性
age — 年龄
name — 名称

在这里插入图片描述

定义 UserBeans 类

User user = new User()
设置 user.age 为 18
设置 user.name 为 bibubibu

在这里插入图片描述

定义 UserController 类

利用属性注入获取 user
将 user 赋值给 u(User u = user)
设置 u.name = Homo

在这里插入图片描述

定义 UserAdviceController 类

利用属性注入获取 user
打印此时的 user

在这里插入图片描述

运行结果

在这里插入图片描述

结果分析🍂

对于 UserController 类, 利用属性注入获取 user
对于 UserAdviceController 类, 利用属性注入获取 user
分属于不同的类, 但为何在 UserController 类中设置 u.name = Homo, 却在 UserAdviceController 类中获取到的 user 为 Homo 而不是 bibubibu?

因为 Bean 的作用域为全局共享
Bean 默认的作用域为 singleton — 单例作用域(全局共享), 因此修改 u.name 时 user.name 也被一并修改

singleton — 单例作用域


描述🍭

该作用域下的 Bean 在 IOC 容器中只存在一个实例
即获取 Bean(ApplicationContext.getBean())与装配 Bean(@Autowired)都是针对同一个对象
(Bean 的默认作用域为 singleton — 单例作用域)

适用场景🍭

通常无状态的 Bean 使用该作用域
即不会对 Bean 进行修改(只读)

为什么 Spring 选择 singleton 作为 Bean 的默认作用域🍭

单例模式只在第一次加载时速度较慢
在后续使用时可直接使用, 无需等待
因此其性能最优

prototype — 原型作用域


prototype 也可以称为多例作用域(对比单例作用域)

描述🍭

每次对该作用域下的 Bean 的请求都会创建一个新的实例(深拷贝)
即获取 Bean(ApplicationContext.getBean())与装配 Bean(@Autowired)都是针对新的对象实例

适用场景🍭

通常有状态的 Bean 使用该作用域
即会对 Bean 进行修改

request — 请求作用域


描述🍭

每次 HTTP 请求都会创建一个新的实例
一次 HTTP 的请求和响应共享同一个 Bean

适用场景🍭

在 SpringMVC 中使用

session — 会话作用域


描述🍭

在一个 HTTP Session 中创建一个新的实例

举个栗子🌰

用户 Jack 登录他的账号, 在 Jack 登陆状态中, Bean 处于共享状态(针对于 Jack 的信息)
另一个用户 Tom 登录他的账号, 在 Tom 登陆状态中, Bean 处于共享状态(针对于 Tom 的信息)
对于 Jack 和 Tom, 他们的 Bean 是隔离而非共享

适用场景🍭

在 SpringMVC 中使用

application — 全局作用域


描述🍭

在一个 HTTP Servlet Context 中创建一个新的实例

适用场景🍭

在 SpringMVC 中使用

websocket — HTTP WebSocket 作用域


描述🍭

在一个 HTTP WebSocket 的生命周期中创建一个新的实例

WebSocket 的每次会话中, 保存了一个 Map 结构的头信息, 用于包裹客户端消息头
第一次初始化后, 直到 WebSocket 结束都是同一个 Bean

适用场景🍭

在 SpringMVC 中使用

对比单例作用域与全局作用域


  1. singleton 是 Spring Core 的作用域
    application 是 Spring Web 的作用域
  2. singleton 作用于 IOC 容器
    application 作用于 Servlet 容器

设置作用域


利用 @Scope() 设置作用域

作用域设置方式
singleton — 单例作用域@Scope("singleton") / @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON))
prototype — 原型作用域@Scope("prototype") / @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
request — 请求作用域@Scope("request") / @Scope(WebApplicationContext.SCOPE_REQUEST)
session — 会话作用域@Scope("session") / @Scope(WebApplicationContext.SCOPE_SESSION)
application — 全局作用域@Scope("application") / @Scope(WebApplicationContext.SCOPE_APPLICATION)

prototype — 原型作用域 为例

在这里插入图片描述

在这里插入图片描述

🔎Spring的执行流程


  1. 启动容器(项目)
  2. 读取xml配置文件, 将 Bean 初始化
    • 直接注册 Bean(<bean id="" class=""></bean>)
    • 配置根扫描路径(<content:component-scan base-package=""></content:component-scan>)
  3. 将 Bean 存储至 Spring
  4. 从 Spring 中读取 Bean

在这里插入图片描述

启动容器(项目)

在这里插入图片描述

读取xml配置文件, 将 Bean 初始化

在这里插入图片描述

将 Bean 存储至 Spring

在这里插入图片描述

从 Spring 中读取 Bean

在这里插入图片描述

🔎Bean的生命周期


Bean 的生命周期分为 5 个部分

  1. 实例化 Bean(为 Bean 分配内存空间)
    实例化是一个从无到有的过程, 将字节码转换成内存中的对象, 分配了内存空间 → 类似于 JVM 的加载过程

  2. 设置属性(Bean 注入)

  3. 初始化 Bean

    • 实现了各种 Aware 的通知方法, 例如 BeanNameAware, BeanFactoryAware…
    • 执行 BeanPostProcessor 初始化的前置方法
    • 执行 @PostConstruct 初始化方法
    • 执行指定的 init-method 初始化方法(如果有)
    • 执行 BeanPostProcessor 初始化的后置方法
  4. 使用 Bean

  5. 销毁 Bean

对比 @PostConstruct 与 init-method

相同🍭

都是初始化方法

不同🍭

@PostConstruct 使用注解进行初始化
init-method 使用 xml 进行初始化(<bean></bean>)

执行顺序 @PostConstruct > init-method

对Bean的生命周期的解释


实例化🍂

类似于购买了一套毛坯房

设置属性(Bean 注入)🍂

为毛坯房挑选装修风格, 装修材料(引入外部资源)

Bean 初始化🍂

装修毛坯房

  • 实现了各种 Aware 的通知方法🍂
    雇用各种装修工人进行装修(水工, 电工, 瓦工…)

  • 执行 BeanPostProcessor 初始化的前置方法🍂
    装修工人到达施工现场, 制定装修方案

  • 执行初始化方法🍂
    有 2 批装修工人
    一批使用现代化装修工具但经验不足(@PostConstruct)
    另一批使用传统装修工具但经验充足(init-method)
    注解的出现时间对比xml较晚
    优先雇用使用现代化装修工具但经验不足的工人 → 执行顺序 @PostConstruct > init-method

  • 执行 BeanPostProcessor 初始化的后置方法🍂
    完成装修任务的工人做的一些善后工作(清理装修的垃圾…)

使用Bean🍂

购房者搬进装修好的房子

销毁Bean🍂

购房者将房子卖掉

示例


在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

示例代码🍂

package com.demo.component;

import org.springframework.beans.factory.BeanNameAware;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

//@Component
public class BeanLifeComponent implements BeanNameAware {


    @Override
    public void setBeanName(String s) {
        System.out.println("执行了通知");
    }

    public void init() {
        System.out.println("执行了 Init-method");
    }

    @PostConstruct
    public void postConstruct() {
        System.out.println("执行了 @PostConstruct");
    }


    @PreDestroy
    public void preDestroy() {
        System.out.println("执行 PreDestroy — 销毁方法");
    }
}

配置文件🍂

<?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.demo"></content:component-scan>
        <bean id="myComponent" class="com.demo.component.BeanLifeComponent"
              init-method="init"></bean>
</beans>

示例代码🍂

public static void main(String[] args) {
    // ClassPathXmlApplicationContext 包含销毁方法
    // ApplicationContext 不包含销毁方法
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");

    BeanLifeComponent lifeComponent = context.getBean("myComponent", BeanLifeComponent.class);

    System.out.println("使用 Bean");
    // 销毁 Bean
    context.destroy();
}

🔎结尾

创作不易,如果对您有帮助,希望您能点个免费的赞👍
大家有什么不太理解的,可以私信或者评论区留言,一起加油

  • 21
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值