Spring Bean 全方位指南:从作用域、生命周期到自动配置详解

目录

1. Bean 的作用域

1.1 singleton

1.2 prototype

1.3 request

1.4 session

1.5 application

1.5.1 servletContext 和 applicationContext 区别

2. Bean 的生命周期

2.1 详解初始化

2.1.1 Aware 接口回调

2.1.2 执行初始化方法

2.2 代码示例

2.3 源码 [面试题]

3. SpringBoot 自动配置

3.1 问题复现

3.2 解决方法

 3.2.1 @ComponentScan

3.2.2 @Import({第三方类.class})

3.2.3 @Import(MySelector.class)

 3.2.4 第三方 jar 中定义注解

3.3 源码解读


1. Bean 的作用域

Bean 的作用域, 这里的 "作用域" 是指 Bean 在 Spring 框架中不同的行为模式.

举个例子, 当 Spring 容器中相同名称的 Bean 只有一个时, 我们无论是通过 context.getBean 获取还是通过 @Autowired 获取, 获取到的 Bean 都是同一个对象(内存地址相同).

这就是 Spring Bean 默认的作用域/行为模式 --- 单例作用域.

Bean 的作用域有以下 6 种:

作用域说明
singleton(单例作用域)每个 Spring IoC 容器内同名称的 bean 只有一个实例 (默认).
prototype(原型/多例作用域)每次获取 bean 时会创建新的实例 (非单例)
request(请求作用域)每个 HTTP 请求生命周期内,创建新的实例 (web 环境中,了解)
session(会话作用域)每个 HTTP Session 生命周期内,创建新的实例 (web 环境中,了解)
application(全局作用域)每个 ServletContext 生命周期内,创建新的实例 (web 环境中,了解)
websocket(HTTP WebSocket 作用域)每个 WebSocket 生命周期内,创建新的实例 (web 环境中,了解)

1.1 singleton

singleton 是 Spring 应用设计模式中单例模式的体现.

singleton 单例作用域, 也是 Spring 默认的作用域, 指在 Spring 容器中, 相同名称的 Bean 只有一个.

如上图所示, 只要获取的是同一个 Bean name 的 Bean, 不管是 @Resource 获取, 还是 getBean  获取, 又或者是不同的客户端(google, postman, ...)获取, 获取到的都是同一个 Bean.

1.2 prototype

多例模式, 每次获取 Bean 时, 都会创建一个新的实例(无论 Bean name 是否相同)

如上图所示, 每发送一个请求, 通过 applicationContext 获取的 bean 就发生了变化. 但是, 通过 @Resource 获取的 bean 却一直不变.

这是因为: 每请求一次, context 就会重新执行一次 getBean 方法(重新获取 bean), 而我们设置的作用域是 prototype, 自然每次获取到的都是一个新的 bean.

但是, 通过 @Resource 获取 bean 赋值给属性这一步骤, 是在项目启动时执行获取 bean 的, 只会执行这一次, 因此只会获取一次 bean. 因此无论请求多少次, 这个属性的值依然是之前的 bean, 是不会变的(除非项目重新启动).

@Resource/@Autowired 是在项目启动时, 底层调用的是 ApplicationContext.getBean 获取 bean 的.

1.3 request

作用域为 request 时, 每次请求, 都会创建一个全新的实例. 

但在同一个请求内是单例的(同一次请求中, 如果 bean name 相同, 那么获取到的是相同的 bean).

1.4 session

作用域为 session 时, 同一个会话中, 同一个 bean name, 获取到的都是同一个 bean.

1.5 application

作用域为 application 时, 同一个 servletContext 中, 获取到的同一个 bean.

就目前来看, application 和 singleton 的效果是一致的, 接下来为大家介绍一下两者区别.

1.5.1 servletContext 和 applicationContext 区别

servletContext 可以认为是一个 Tomcat 服务器, 一个 Tomcat 服务器中, 可以部署多个服务, 其中每一个服务是一个 applicationContext.

因此, 一个 servletContext 可以包含多个 applicationContext.

但是目前, 我们都是基于 Spring 进行开发, 而 Spring 集成了 Tomcat, 一个 Tomcat 中只运行一个服务, 因此目前的现状是: 一个 Tomcat 只部署一个服务.

因此, 对于 Spring 项目来说, 作用域为 application 和 singleton 的效果是一样的.


2. Bean 的生命周期

bean 的生命周期和我们人的生命周期, 从出生到死亡. 又或者说像一个戏剧演员, 从登台到谢幕...

简洁来说, bean 的生命周期为: 实例化 ➝ 填充属性/依赖注入 ➝ 初始化 ➝ 使用 ➝ 销毁

  1. 实例化: 为 bean 分配内存空间(执行构造方法) 
  2. 属性赋值: 对属性 bean 进行依赖注入
  3. 初始化: 1. 执行各种 aware 通知(BeanNameAware, BeanFactoryAware, ...) 2. 执行初始化方法(@Bean, @PostConstruct, ...)
  4. 使用 bean
  5. 销毁 bean(执行 @PreDestroy 方法)

先打个比方:

  1. 实例化: 买房
  2. 属性赋值: 装修
  3. 初始化: 买家电
  4. 使用 bean: 住房
  5. 销毁 bean: 卖房

2.1 详解初始化

初始化较难理解, 我们这里聚焦初始化这一步.

初始化中, 有两个关键动作:

  1. 执行 aware 接口回调
  2. 执行初始化方法

2.1.1 Aware 接口回调

如果 Bean 实现了 Spring 提供的一些特定的 Aware 接口, 如: BeanNameAware, BeanFactoryAware, ApplicationContextAware, ... 那么在初始化时, Spring 会在初始化阶段调用这些 Aware 接口中定义的方法, 使得 Bean 可以获取到 Spring 容器的相关信息.

这几个接口的作用如下:

  1. BeanNameAware: 获取当前 Bean 的 bean name(告诉你你叫什么名字)

  2. BeanFactoryAware: 获取 BeanFactory, 允许你在 Bean 内部动态访问容器中的其他 Bean.(相当于 Spring 在初始化你时, 告诉你: 这是我们的 Bean 管理中心钥匙, 需要时你可以自己去开柜子拿其他 Bean)

  3. ApplicationContextAware: 获取 ApplicationContext(获取 Spring 配置信息, 告诉你你生长在什么配置环境下)

Aware 接口名要干嘛?你要怎么用?
BeanNameAware想知道自己的名字实现接口:重写 setBeanName(String name)
BeanFactoryAware想拿到 Bean 工厂引用实现接口:重写 setBeanFactory(BeanFactory)
ApplicationContextAware想拿到上下文容器实现接口:重写 setApplicationContext(...)
EnvironmentAware想获取环境变量实现接口:重写 setEnvironment(...)
ResourceLoaderAware想读取资源文件实现接口:重写 setResourceLoader(...)

2.1.2 执行初始化方法

初始化中的第二个关键动作, 即: 执行初始化方法.

定义初始化方法, 有以下三种方式:

方式是否需要你主动声明如何声明
@PostConstruct 注解✅ 要加注解@PostConstruct public void init() {...}
实现 InitializingBean 接口✅ 要重写方法重写 afterPropertiesSet() 方法
initMethod 属性✅ 要在配置里写@Bean(initMethod = "xxx")或XML

注意: 无论是 Aware 回调,还是初始化方法,确实都需要你“主动声明” —— 要么实现接口,要么加注解,要么在配置中写明 !!

  • Aware 回调:靠你“实现接口”,Spring 才知道你“想要这个功能”
  • 初始化方法:你也得“声明想执行”,才会触发

2.2 代码示例

@Component
public class BeanLifeComponent implements BeanNameAware {// 注入 bean: 1. @Autowired... 2. 构造方法 3. set// 这里通过 set 方法注入private DogComponent dogComponent;// 1. 实例化public BeanLifeComponent() {System.out.println("1. 实例化, 执行构造方法");}// 2. 构造方法注入属性 bean@Autowiredpublic void setDogComponent(DogComponent dogComponent) {this.dogComponent = dogComponent;System.out.println("2. 属性注入");}// 3. 执行 Aware 回调@Overridepublic void setBeanName(String name) {System.out.println("3. Aware 回调, bean name: " + name);}// 4. 执行初始化方法@PostConstructpublic void init() {System.out.println("4. 执行初始化方法");}// 5. 使用 Beanpublic void use() {System.out.println("5. 使用 Bean");}// 6. 销毁 Bean@PreDestroypublic void destroy() {System.out.println("6. 销毁 bean");}
}

执行结果如下:

2.3 源码 [面试题]

Bean 生命周期的核心流程在 AbstractAutowireCapableBeanFactory 的 createBean 方法中.

1. 实例化: 根据类的全限定名, 获取类的 .class, 从 BeanFactory 中执行 Bean 的构造方法, 创建 Bean 的实例.

2. 属性注入: 先查看有没有属性, 如果有的话, 判断 Bean 是根据 Bean name 注入的还是根据类型注入的, 分别执行不同的方法, 进行属性注入

3. 初始化过程, 在源码中分为四部分:

  1. 检查 Bean 实现了哪些 Aware 接口, 并执行其中的执行 Aware 回调方法
  2. 执行初始化前置方法
  3. 执行初始化方法
  4. 执行初始化后置方法

 4. Bean 使用

5. 执行 Bean 销毁注解的方法


3. SpringBoot 自动配置

通过 Maven 将第三方 JAR 包的依赖引入项目中后, 然后这些第三方 jar 提供的 Bean, 就能通过注解 "自动地" 注入到我们的项目, 而不需要我们手动写 @ComponentScan 指定它的包, 或者写 @Import 去导入它的配置类(不受启动类目录限制/不受 Spring 扫描路径的限制) —— 这就是 Spring Boot 自动配置.

3.1 问题复现

为了更好的让大家理解 Spring 的自动配置, 我们先模拟一下没有 "自动配置" 的场景.

我们新建一个 config 包, 模拟第三方 jar 代码:

我们使用 @Configuration 将第三方代码中的 Bean 交给 Spring 管理.

接着, 在启动类中使用 getBean 获取这些第三方 Bean, 观察是否能执行成功:

答案是显而易见的, Spring 只会扫描启动类所在目录, 不会扫描第三方 jar 代码, 因此 Spring 容器根本没有执行那个第三方 JAR 中的 @Configuration 类, 因此也根本没有管理其中定义的 Bean, 更不会获取到这些 Bean.

3.2 解决方法

解决方法有以下四种:

  1. @ComponentScan(basePackages = "com.config"): 在启动类上, 明确指定要扫描的第三方 JAR 的包路径
  2. @Import({DemoConfig.class, DemoConfig2.class}): 在启动类上, 手动地导入第三方 JAR 中的 @Configuration 类
  3. @Import(MySelector.class): 第三方 jar 实现 ImportSelector 接口, 一次性导入所有第三方 jar 类
  4. 第三方 jar 中自定义注解, 在该注解的定义中使用 @Import 来引用同一个 JAR 包中的其他配置类

 3.2.1 @ComponentScan

@ComponentScan(basePackages = "com.config"): 在启动类上, 明确指定要扫描的第三方 JAR 的包路径.

3.2.2 @Import({第三方类.class})

@Import({DemoConfig.class, DemoConfig2.class}): 在启动类上, 手动地导入第三方 JAR 中的 @Configuration 类

3.2.3 @Import(MySelector.class)

@Import(MySelector.class): 在第三方 jar 中实现 ImportSelector 接口, 一次性导入所有第三方 jar 类

创建自定类 MySelector 实现 ImportSelector 接口, 重写方法, 对要导入的第三方类统一封装.

 3.2.4 第三方 jar 中定义注解

第三方 jar 中自定义注解, 在该注解的定义中使用 @Import 来引用同一个 JAR 包中的其他配置类

Spring 底层也是采用注解的方式, 来完成 自动配置 功能的.

3.3 源码解读

我们知道, Spring 项目启动时, 只会默认扫描启动类所在的目录及其子孙目录, 可是我们通过 Maven 引入的第三方 jar 包中的 Bean 是怎么被 Spring 扫描到, 并交由 Spring 管理的呢?

我们来看源码是怎么做的.

总结:

Spring 的自动配置, 是通过 @SpringBootApplication 注解实现的. @SpringBootApplication 中包含三个关键注解:

  1. @SpringBootConfiguration
  2. @EnableAutoConfiguration
  3. @ComponentScan

其中, @SpringBootConfiguration 可以认为是 @Configuration 的封装, 标识启动类为配置类, 本质也是交给 Spring 管理.

而 @ComponentScan, 指定了 Spring 的默认扫描路径, 也就是扫描启动类所在目录.

最核心的是 @EnableAutoConfiguration 注解, 这个注解中又包含了两个注解:

  1. @AutoConfigurationPackage
  2. @Import(提供了一个 ImportSelector)

其中, @Import 中的 ImportSelector, 让 Spring 能够扫描第三方 jar 包提供的 import 文件中的类(第三方会提供一个 import 文件, 其中包含了要被 Spring 管理的类), 但是 Spring 不会将 import 文件中的所有类都进行管理, 而是会通过 @Condition 条件注解进行筛选, 筛选出要管理的 Bean.

而 @AutoConfigurationPackage 和 @ComponentScan 有点相似, 共同作用使 Spring 默认扫描启动类所在包及其子包.

@AutoConfigurationPackage 和 @ComponentScan 区别:

  • @ComponentScan: 这是真正执行扫描动作的注解, 如果没有指定扫描路径, 会默认按照 @AutoConfigurationPackage 提供的路径即启动类路径进行扫描.
  • @AutoConfigurationPackage: 注册启动类所在的包. 它本身不执行扫描, 它的主要作用是告诉 @ComponentScan 启动类在哪里

面试时, 先总结给出一个高层次的概述, 如果面试官追问细节, 再用这些内容详细去阐述 @ComponentScan 和 @AutoConfigurationPackage 的具体区别.


END 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++ (非类型参数)

模板除了定义类型参数之外,也可以在模板内定义非类型参数 非类型参数不是类型,而是值,比如:指针,整数,引用 非类型参数的用法: 1.整数常量:非类型参数最常见的形式是整数常量&…

短视频+直播商城系统源码全解析:音视频流、商品组件逻辑剖析

时下,无论是依托私域流量运营的品牌方,还是追求用户粘性与转化率的内容创作者,搭建一套完整的短视频直播商城系统源码,已成为提升用户体验、增加商业变现能力的关键。本文将围绕三大核心模块——音视频流技术架构、商品组件设计、…

5.QT-常用控件-QWidget|enabled|geometry|window frame(C++)

控件概述 实现图形化界面的程序. Qt中已经给我们提供了很多的“控件" 就需要学习和了解这些控件,学会如何使用这些控件 编程讲究的是“站在巨人的肩膀上”,而不是“从头发明轮子" 一个图形化界面上的内容,不需要咱们全都从零去实…

2025-04-22| Docker: --privileged参数详解

在 Docker 中,--privileged 是一个运行容器时的标志,它赋予容器特权模式,大幅提升容器对宿主机资源的访问权限。以下是 --privileged 的作用和相关细节: 作用 完全访问宿主机的设备: 容器可以访问宿主机的所有设备&am…

高性能服务器配置经验指南1——刚配置好服务器应该做哪些事

文章目录 安装ubuntu安装必要软件设置用户远程连接安全问题ClamAV安装教程步骤 1:更新系统软件源步骤 2:升级系统(可选但推荐)步骤 3:安装 ClamAV步骤 4:更新病毒库步骤 5:验证安装ClamAV 常用命…

直流绝缘监测解决方案:保障工业与新能源系统的安全运行

一、引言 随着工业自动化和新能源技术的快速发展,直流供电系统在光伏发电、储能电站、电动汽车充电桩等领域的应用日益广泛。然而,直流系统的正负极不接地(IT系统)特性,使得绝缘故障可能导致漏电、短路甚至设备损毁等…

VSCode 用于JAVA开发的环境配置,JDK为1.8版本时的配置

插件安装 JAVA开发在VSCode中,需要安装JAVA的必要开发。当前安装只需要安装 “Language Support for Java(TM) by Red Hat”插件即可 安装此插件后,会自动安装包含如下插件,不再需要单独安装 Project Manager for Java Test Runner for J…

C++入门语法

C入门 首先第一点,C中可以混用C语言中的语法。但是C语言是不兼容C的。C主要是为了改进C语言而创建的一门语言,就是有人用C语言用不爽了,改出来个C。 命名空间 c语言中会有如下这样的问题: 那么C为了解决这个问题就整出了一个命名…

输入框仅支持英文、特殊符号、全角自动转半角 vue3

需求&#xff1a;封装一个输入框组件 1.只能输入英文。 2.输入的小写英文自动转大写。 3.输入的全角特殊符号自动转半角特殊字符 效果图 代码 <script setup> import { defineEmits, defineModel, defineProps } from "vue"; import { debounce } from "…

Uniapp:创建项目

目录 一、前提准备二、创建项目三、项目结构四、运行测试 一、前提准备 首先要创建uniapp项目&#xff0c;需要先下载HBuilderX&#xff0c;HBuilderX是一款开箱即用的工具&#xff0c;下载完毕之后&#xff0c;解压到指定的目录即可使用&#xff0c;需要注意的是最好路径里面…

ESM 内功心法:化解 require 中的夺命一击!

前言 传闻在JavaScript与TypeScript武林中,曾有两大绝世心法:CommonJS与ESM。两派高手比肩而立,各自称霸一方,江湖一度风平浪静。 岂料,时局突变。ESM逐步修成阳春白雪之姿,登堂入室,成为主流正统。CommonJS则渐入下风,功力不济,逐渐退出主舞台。 话说某日,一位前…

【STL】unordered_set

在 C C C 11 11 11 中&#xff0c; S T L STL STL 标准库引入了一个新的标准关联式容器&#xff1a; u n o r d e r e d _ s e t unordered\_set unordered_set&#xff08;无序集合&#xff09;。功能和 s e t set set 类似&#xff0c;都用于存储唯一元素。但是其底层数据结…

go语言八股文

1.go语言的接口是怎么实现 接口&#xff08;interface&#xff09;是一种类型&#xff0c;它定义了一组方法的集合。任何类型只要实现了接口中定义的所有方法&#xff0c;就被认为实现了该接口。 代码的实现 package mainimport "fmt"// 定义接口 type Shape inte…

kafka auto.offset.reset详解

在 Kafka 中&#xff0c;auto.offset.reset latest 的含义及行为如下&#xff1a; 1. ​​核心定义​​ 当消费者组​​首次启动​​或​​无法找到有效的 offset​​&#xff08;例如 offset 过期、被删除或从未提交&#xff09;时&#xff0c;消费者会从分区的​​最新位置…

深度学习-损失函数

目录 1. 线性回归损失函数 1.1 MAE损失 1.2 MSE损失 2. CrossEntropyLoss 2.1 信息量 2.2 信息熵 2.3 KL散度 2.4 交叉熵 3. BCELoss 4. 总结 1. 线性回归损失函数 1.1 MAE损失 MAE&#xff08;Mean Absolute Error&#xff0c;平均绝对误差&#xff09;通常也被称…

第六篇:linux之解压缩、软件管理

第六篇&#xff1a;linux之解压缩、软件管理 文章目录 第六篇&#xff1a;linux之解压缩、软件管理一、解压和压缩1、window压缩包与linux压缩包能否互通&#xff1f;2、linux下压缩包的类型3、打包与压缩 二、软件管理1、rpm1、什么是rpm&#xff1f;2、rpm包名组成部分3、如何…

Redis 键管理

Redis 键管理 以下从键重命名、随机返回键、键过期机制和键迁移四个维度展开详细说明&#xff0c;结合 Redis 核心命令与底层逻辑进行深入分析&#xff1a; 一、键重命名 1. ​RENAME​​ 与 ​RENAMENX​​ **RENAME key newkey​**&#xff1a; 功能&#xff1a;强制重命名…

OpenCV 模板匹配方法详解

文章目录 1. 什么是模板匹配&#xff1f;2. 模板匹配的原理2.1数学表达 3. OpenCV 实现模板匹配3.1基本步骤 4. 模板匹配的局限性5. 总结 1. 什么是模板匹配&#xff1f; 模板匹配&#xff08;Template Matching&#xff09;是计算机视觉中的一种基础技术&#xff0c;用于在目…

TextCNN 模型文本分类实战:深度学习在自然语言处理中的应用

在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;文本分类是研究最多且应用最广泛的任务之一。从情感分析到主题识别&#xff0c;文本分类技术在众多场景中都发挥着重要作用。最近&#xff0c;我参与了一次基于 TextCNN 模型的文本分类实验&#xff0c;从数据准备到…

Qt-创建模块化.pri文件

文章目录 一、.pri文件的作用与基本结构作用基本结构 二、创建.pri文件如何添加模块代码&#xff1f; 一、.pri文件的作用与基本结构 作用 在Qt开发中&#xff0c;.pri文件&#xff08;Project Include File&#xff09;是一种配置包含文件&#xff0c;用于模块化管理和复用项…