Bean的作用域和生命周期
- 🔎前置
- 引入 Lombok 相关依赖
- 下载 Lombok 插件
- 🔎Bean的6种作用域
- 对Bean作用域的解释
- singleton — 单例作用域
- prototype — 原型作用域
- request — 请求作用域
- session — 会话作用域
- application — 全局作用域
- websocket — HTTP WebSocket 作用域
- 对比单例作用域与全局作用域
- 设置作用域
- 🔎Spring的执行流程
- 🔎Bean的生命周期
- 对Bean的生命周期的解释
- 示例
- 🔎结尾
🔎前置
引入 Lombok
- 引入 Lombok 相关依赖
- 下载 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 中使用
对比单例作用域与全局作用域
- singleton 是 Spring Core 的作用域
application 是 Spring Web 的作用域 - 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的执行流程
- 启动容器(项目)
- 读取
xml
配置文件, 将 Bean 初始化- 直接注册 Bean(
<bean id="" class=""></bean>
) - 配置根扫描路径(
<content:component-scan base-package=""></content:component-scan>
)
- 直接注册 Bean(
- 将 Bean 存储至 Spring
- 从 Spring 中读取 Bean
启动容器(项目)
读取xml
配置文件, 将 Bean 初始化
将 Bean 存储至 Spring
从 Spring 中读取 Bean
🔎Bean的生命周期
Bean 的生命周期分为 5 个部分
-
实例化 Bean(为 Bean 分配内存空间)
实例化是一个从无到有的过程, 将字节码转换成内存中的对象, 分配了内存空间 → 类似于 JVM 的加载过程 -
设置属性(Bean 注入)
-
初始化 Bean
- 实现了各种 Aware 的通知方法, 例如 BeanNameAware, BeanFactoryAware…
- 执行 BeanPostProcessor 初始化的前置方法
- 执行 @PostConstruct 初始化方法
- 执行指定的 init-method 初始化方法(如果有)
- 执行 BeanPostProcessor 初始化的后置方法
-
使用 Bean
-
销毁 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 {@Overridepublic void setBeanName(String s) {System.out.println("执行了通知");}public void init() {System.out.println("执行了 Init-method");}@PostConstructpublic void postConstruct() {System.out.println("执行了 @PostConstruct");}@PreDestroypublic 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");// 销毁 Beancontext.destroy();
}
🔎结尾
创作不易,如果对您有帮助,希望您能点个免费的赞👍
大家有什么不太理解的,可以私信或者评论区留言,一起加油