循序渐进学 Spring (上):从 IoC/DI 核心原理到 XML 配置实战
文章目录
- 0. 写在前面
- 1. Spring 核心概念
- 1.1 简介
- 1.2 优点
- 1.3 组成 (Spring Framework Modules)
- 1.4 拓展
- 2. IoC 理论推导 (控制反转)
- 2.1 从一个实例开始理解
- 步骤一:传统开发方式(主动创建依赖)
- 步骤二:改进方式(通过 Setter 注入)
- 2.2 IoC 的本质
- 3. 第一个 Spring 程序 (HelloSpring)
- 核心概念回顾
- 4. IoC 创建对象的方式
- 4.1 方式一:使用无参构造函数 (默认)
- 4.2 方式二:使用有参构造函数
- 5. Spring 配置详解
- 5.1 别名 (`<alias>`)
- 5.2 Bean 的基本配置 (`id`, `class`, `name`)
- 5.3 导入其他配置 (`<import>`)
- 6. 依赖注入 (DI)
- 6.1 构造器注入
- 6.2 Set 注入 (重点)
- 6.3 拓展方式注入 (p-命名空间和c-命名空间)
- 6.4 Bean 的作用域 (`scope`)
- 参考
0. 写在前面
你好,我是 ZzzFatFish。这篇博客整理自我学习 Spring 框架的笔记。目前笔记只记录到一半,但内容恰好完整覆盖了 Spring 最核心、最基础、也是最重要的概念——控制反转 (IoC) 与依赖注入 (DI)。
即使在 Spring Boot 已成为主流的今天,深入理解 Spring Framework 的底层原理依然是 Java 开发者进阶的必经之路。希望这份笔记能帮助正在学习 Spring 的你,理清思路,夯实基础。
学习是一个持续迭代的过程,本文后续会继续更新。所有笔记对应的源码都已上传至我的 Gitee 仓库,欢迎 Star 和交流!
📦 对应代码仓库:https://gitee.com/zzzfatfish/spring-test
✍ 作者:fatfish
🕒 状态:学习中,内容持续补充完善…
1. Spring 核心概念
1.1 简介
Spring 的寓意
Spring,意为“春天”,旨在为软件开发带来春天般的生机与活力。
发展历史
- 2002年:Rod Johnson(悉尼大学音乐学博士)发布了 Spring 框架的雏形——
Interface21
框架。 - 2004年:在
Interface21
的基础上,经过重新设计和功能丰富,Spring 1.0 正式版发布。 - 核心理念:Spring 的目标是让现有的 Java 技术(如 Java EE)更加易于使用。它本身是一个集大成者,通过整合各种优秀的开源框架,为开发者提供一站式的解决方案。
经典技术栈
- SSH: Struts2 + Spring + Hibernate
- SSM: Spring MVC + Spring + MyBatis
相关资源
- 官网: https://spring.io/
- 项目主页: https://spring.io/projects/spring-framework
- 官方文档: Spring Framework Documentation
- Maven 仓库: https://mvnrepository.com/
Maven 核心依赖示例
要在项目中使用 Spring,通常需要引入其模块依赖。例如,开发 Web 应用需要 spring-webmvc
,使用 JDBC 需要 spring-jdbc
。
<!-- Spring Web MVC 模块,包含了核心容器、AOP、Web等功能 -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.3.23</version> <!-- 建议使用较新的稳定版本 -->
</dependency><!-- Spring JDBC 模块,用于简化数据库操作 -->
<dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.3.23</version>
</dependency>
1.2 优点
- 开源免费:Spring 是一个开源的、免费的框架(容器)。
- 轻量级与非侵入式:
- 轻量级:指其核心库体积小,对系统资源占用少,并且可以按需引入模块。
- 非侵入式:应用中的对象(POJO)无需继承 Spring 特定的类或实现接口,保持了代码的独立性。
- 两大核心:
- 控制反转 (IoC - Inversion of Control):将对象的创建权交给 Spring 容器管理。
- 面向切面编程 (AOP - Aspect Oriented Programming):在不修改源码的情况下,对程序进行功能增强。
- 生态强大:
- 提供强大的事务管理能力。
- 能够轻松整合几乎所有主流的第三方框架(如 MyBatis, Hibernate, Quartz 等)。
总结:Spring 是一个以 IoC(控制反转)和 AOP(面向切面编程)为核心的,一站式的轻量级开源框架。
1.3 组成 (Spring Framework Modules)
Spring 框架是一个分层架构,由多个功能明确的模块组成,可以根据项目需求按需引入。其核心模块主要包括:
-
Core Container (核心容器)
spring-core
,spring-beans
,spring-context
,spring-expression
- 这是 Spring 框架的基石,提供了 IoC 和 DI 的功能。
BeanFactory
和ApplicationContext
是其核心接口。
-
AOP & Aspects (面向切面编程)
spring-aop
,spring-aspects
- 提供了 AOP 的实现,允许开发者定义方法拦截器和切点,实现如日志、权限控制等横切关注点的功能。
-
Data Access / Integration (数据访问与集成)
spring-jdbc
: 简化了 JDBC 操作。spring-orm
: 集成了主流的 ORM 框架,如 Hibernate、JPA。spring-tx
: 提供了强大的声明式和编程式事务管理。spring-jms
,spring-messaging
: 用于消息传递。
-
Web
spring-web
: 提供了基础的 Web 功能,如文件上传、HTTP 客户端等。spring-webmvc
: 包含了 Spring MVC 框架,用于构建 Web 应用程序。spring-websocket
: 支持 WebSocket 通信。
1.4 拓展
为什么现在还要学习 Spring?
虽然 Spring Boot 和 Spring Cloud 是目前的主流,但它们都是构建在 Spring Framework 之上的。
- Spring Boot: 一个快速开发的脚手架,遵循“约定大于配置”的原则,极大地简化了 Spring 应用的配置和部署。
- Spring Cloud: 基于 Spring Boot 的一套微服务治理工具集。
学习路径:深入理解 Spring 和 Spring MVC 的原理,是掌握 Spring Boot 的前提。它是承上启下的关键。
Spring 的“弊端”
随着功能不断扩展,早期 Spring 的 XML 配置变得异常繁琐和复杂,导致项目难以维护,被戏称为“配置地狱”。Spring Boot 的出现正是为了解决这一问题,通过自动化配置让开发者回归到业务本身。
2. IoC 理论推导 (控制反转)
2.1 从一个实例开始理解
我们通过一个简单的例子来理解 IoC 的思想演变。假设我们有一个 UserService
,它需要调用 UserDao
来获取数据。
项目结构:
UserDao
(接口)UserDaoImpl
(实现类)UserService
(接口)UserServiceImpl
(实现类)
步骤一:传统开发方式(主动创建依赖)
UserDao 接口 & 实现
// com.github.subei.dao.UserDao
public interface UserDao {void getUser();
}// com.github.subei.dao.UserDaoImpl
public class UserDaoImpl implements UserDao {@Overridepublic void getUser() {System.out.println("默认实现:从数据库获取用户数据...");}
}
UserService 接口 & 实现
// com.github.subei.service.UserService
public interface UserService {void getUser();
}// com.github.subei.service.UserServiceImpl
import com.github.subei.dao.UserDao;
import com.github.subei.dao.UserDaoImpl;public class UserServiceImpl implements UserService {// 程序主动创建依赖对象,耦合度高private UserDao userDao = new UserDaoImpl();@Overridepublic void getUser() {userDao.getUser();}
}
测试类
// MyTest.java
import com.github.subei.service.UserServiceImpl;public class MyTest {public static void main(String[] args) {UserServiceImpl userService = new UserServiceImpl();userService.getUser();}
}
问题分析:在 UserServiceImpl
中,userDao
是通过 new UserDaoImpl()
直接创建的。如果此时客户需求变更,需要从 Oracle 数据库获取数据(UserDaoOracleImpl
),我们必须修改 UserServiceImpl
的源代码。这种高耦合的方式在大型项目中是灾难性的。
步骤二:改进方式(通过 Setter 注入)
为了解决上述问题,我们不再让 UserServiceImpl
自己创建 UserDao
,而是提供一个 Setter 方法,让外部来决定使用哪个实现。
修改 UserServiceImpl.java
// com.github.subei.service.UserServiceImpl
import com.github.subei.dao.UserDao;public class UserServiceImpl implements UserService {private UserDao userDao;// 提供一个 set 方法,用于接收外部传入的依赖对象public void setUserDao(UserDao userDao) {this.userDao = userDao;}@Overridepublic void getUser() {userDao.getUser();}
}
修改测试类
现在,我们可以在测试类中“注入”我们想要的 UserDao
实现。
// 假设我们新增了一个 UserDaoMySqlImpl
public class UserDaoMySqlImpl implements UserDao {@Overridepublic void getUser() {System.out.println("新实现:从MySQL获取用户数据...");}
}// MyTest.java
import com.github.subei.dao.UserDaoMySqlImpl;
import com.github.subei.service.UserServiceImpl;public class MyTest {public static void main(String[] args) {UserServiceImpl userService = new UserServiceImpl();// 关键改变:我们在这里决定并“注入”具体的实现userService.setUserDao(new UserDaoMySqlImpl());userService.getUser();}
}
思想转变:
- 之前:
UserServiceImpl
主动创建并控制它所依赖的UserDao
对象。 - 现在:
UserServiceImpl
失去了对UserDao
的创建控制权,它只能被动地接收。控制权反转到了调用方(MyTest
)手中。
这种思想就是 控制反转 (IoC) 的雏形。程序从主动创建依赖,变成了被动接收依赖,从而大大降低了组件之间的耦合度。
2.2 IoC 的本质
控制反转 (Inversion of Control, IoC) 是一种设计思想,而不是一种具体的技术。它的核心是将原本由程序代码直接操控的对象创建权,交由一个外部容器来管理。
依赖注入 (Dependency Injection, DI) 是实现 IoC 最常见的方式。所谓“依赖注入”,就是指容器在运行时,动态地将某个对象所依赖的其它对象注入到该对象中。
总结:
-
谁控制谁?
- 传统方式:应用程序的组件(如
UserServiceImpl
)控制它所依赖的对象(UserDaoImpl
)的创建。 - IoC 方式:IoC 容器(如 Spring 容器)控制了所有组件的创建。组件只负责声明自己的依赖,不负责创建。
- 传统方式:应用程序的组件(如
-
控制什么?
- 主要控制外部资源的获取,例如对象实例、文件、数据库连接等。
-
为何叫反转?
- 因为获取依赖的方向反了。传统方式是组件主动去获取依赖,而 IoC 方式是容器将依赖“推送”或“注入”到组件中。
-
Spring 如何实现 IoC?
- Spring IoC 容器(
ApplicationContext
)在启动时,会读取配置元数据(XML 文件或注解)。 - 根据元数据创建和装配所有的对象(在 Spring 中称为 Bean)。
- 当应用程序需要某个对象时,直接从容器中获取即可,无需关心其创建过程和依赖关系。
- Spring IoC 容器(
通过 IoC,我们将对象的创建、管理和装配等繁杂工作交给了框架,使我们能更专注于业务逻辑的实现。这就是 Spring 框架的强大之处。
3. 第一个 Spring 程序 (HelloSpring)
通过一个简单的入门案例,我们来实际感受一下 Spring IoC 容器是如何工作的。
步骤 1: 创建实体类 Hello.java
这是一个普通的 POJO (Plain Old Java Object)。
package com.github.subei.pojo;public class Hello {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Hello{" +"name='" + name + '\'' +'}';}
}
步骤 2: 创建 Spring 配置文件 beans.xml
这个 XML 文件是 Spring IoC 容器的“蓝图”,它描述了需要容器管理的对象(Bean)以及它们之间的关系。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!-- 使用 <bean> 标签告诉 Spring 创建一个对象。在 Spring 中,这些被管理的对象称为 Bean。id: Bean 的唯一标识符,相当于对象名。class: Bean 的全限定类名。--><bean id="hello" class="com.github.subei.pojo.Hello"><!-- 使用 <property> 标签为对象的属性赋值。name: 对应类中的属性名 (setter方法)。value: 要设置的具体值。--><property name="name" value="Spring"/></bean></beans>
步骤 3: 编写测试类
import com.github.subei.pojo.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class MyTest {public static void main(String[] args) {// 1. 获取 Spring 的上下文对象 (IoC 容器)// ClassPathXmlApplicationContext 会解析 beans.xml 文件, 创建并管理其中定义的 BeanApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");// 2. 从容器中获取我们需要的 Bean// context.getBean("id"),参数即为 Spring 配置文件中 bean 的 idHello hello = (Hello) context.getBean("hello");// 3. 使用对象System.out.println(hello.toString());}
}
核心概念回顾
-
Hello
对象是谁创建的?
Hello
对象由 Spring IoC 容器创建,而不是我们手动new
的。 -
Hello
对象的属性是怎么设置的?
name
属性是在beans.xml
中配置后,由 Spring 容器通过调用setName()
方法注入的。
这个过程就是控制反转 (IoC) 的体现:
- 控制:对
Hello
对象创建和管理的控制权。 - 反转:控制权从我们的应用程序代码(手动
new
)反转到了外部的 Spring 容器。
依赖注入 (DI) 则是实现 IoC 的一种方式,即容器将对象所需的依赖(如 name
属性值)通过 Setter 方法或构造函数注入进去。
将 IoC 应用于上一节的例子
现在,我们用 Spring 的方式来改造 UserService
和 UserDao
的例子,彻底实现解耦。
beans.xml
配置
<?xml version="1.0" encoding="UTF-8"?>
<beans ...><!-- 把 UserDao 的两个实现类都交给 Spring 管理 --><bean id="mysqlImpl" class="com.github.subei.dao.UserDaoMySqlImpl"/><bean id="oracleImpl" class="com.github.subei.dao.UserDaoOracleImpl"/><!-- 配置 UserService --><bean id="userServiceImpl" class="com.github.subei.service.UserServiceImpl"><!-- 使用 ref 引用容器中已经存在的另一个 Bean。这里我们将 id 为 "mysqlImpl" 的 Bean 注入到 userDao 属性中。--><property name="userDao" ref="mysqlImpl"/></bean></beans>
测试类
@Test
public void testUserService() {ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");UserServiceImpl service = (UserServiceImpl) context.getBean("userServiceImpl");service.getUser(); // 输出:从MySQL获取用户数据...
}
优势:现在,如果想切换到 Oracle 数据库,我们只需修改 XML 配置文件,将 ref
的值从 mysqlImpl
改为 oracleImpl
,而无需改动任何一行 Java 代码。这真正实现了“对象由 Spring 来创建、管理和装配”。
4. IoC 创建对象的方式
Spring 容器创建 Bean 主要通过反射机制,常见的方式有两种:
4.1 方式一:使用无参构造函数 (默认)
当我们在 XML 中只配置 <bean class="...">
时,Spring 默认会调用该类的无参构造函数来创建实例。如果类中没有无参构造函数,Spring 容器会启动失败。
public class User {public User() {System.out.println("User 的无参构造函数被调用!");}// ... getters and setters
}
配置
<bean id="user" class="com.github.subei.pojo.User"><property name="name" value="Subei"/>
</bean>
结论:在 context.getBean("user")
之前,当 ApplicationContext
初始化的那一刻,User
对象就已经通过无参构造函数被创建好了。
4.2 方式二:使用有参构造函数
如果希望 Spring 使用有参构造函数来创建对象,需要使用 <constructor-arg>
标签明确指定构造函数的参数。
public class User {private String name;public User(String name) {this.name = name;System.out.println("User 的有参构造函数被调用!");}// ...
}
配置方式
<!-- 方式一:通过参数下标 (index) 指定,从 0 开始 -->
<bean id="user1" class="com.github.subei.pojo.User"><constructor-arg index="0" value="Subei_by_index"/>
</bean><!-- 方式二:通过参数类型 (type) 指定,不推荐,类型相同时会混淆 -->
<bean id="user2" class="com.github.subei.pojo.User"><constructor-arg type="java.lang.String" value="Subei_by_type"/>
</bean><!-- 方式三:通过参数名 (name) 指定,最推荐,直观且不易出错 -->
<bean id="user3" class="com.github.subei.pojo.User"><constructor-arg name="name" value="Subei_by_name"/>
</bean>
总结:在配置文件加载时,Spring 容器就会根据配置调用相应的构造函数来初始化 Bean。
5. Spring 配置详解
5.1 别名 (<alias>
)
可以为一个 Bean 设置一个或多个别名,通过别名也能获取到同一个对象。
<bean id="user" class="com.github.subei.pojo.User" ... /><!-- 为 id="user" 的 bean 设置一个别名 "userNew" -->
<alias name="user" alias="userNew"/>
之后,context.getBean("user")
和 context.getBean("userNew")
获取的是同一个实例。
5.2 Bean 的基本配置 (id
, class
, name
)
<!--id: Bean 的唯一标识符。命名规范遵循 XML ID 约束(不能以数字开头等)。class: Bean 的全限定类名。name: 也是别名。相比 <alias> 标签,name 属性可以一次性指定多个别名,用逗号(,)、分号(;)或空格分隔。name 属性命名更灵活,没有 id 的限制。
-->
<bean id="user" class="com.github.subei.pojo.User" name="u2, u3; u4"><property name="name" value="懒羊羊"/>
</bean>
context.getBean("user")
, context.getBean("u2")
, context.getBean("u3")
, context.getBean("u4")
都能获取到这个 Bean。
5.3 导入其他配置 (<import>
)
在团队开发或大型项目中,为了便于管理,通常会将 Spring 配置拆分成多个文件(如按模块 spring-dao.xml
, spring-service.xml
)。可以使用 <import>
标签将它们组合起来。
<!-- 在主配置文件 applicationContext.xml 中 -->
<beans ...><import resource="spring-dao.xml"/><import resource="spring-service.xml"/><import resource="spring-controller.xml"/>
</beans>
6. 依赖注入 (DI)
依赖注入是 IoC 的具体实现。它的核心思想是:
- 依赖:Bean 的创建和运行依赖于 Spring 容器。
- 注入:Bean 的属性(包括基本类型和对象类型)由 Spring 容器来设置和装配。
6.1 构造器注入
已在 4.2
节中介绍,通过 <constructor-arg>
标签实现。
6.2 Set 注入 (重点)
通过调用属性的 setter
方法进行注入,使用 <property>
标签实现。这是最常用、最直观的注入方式。
示例环境
// 地址类
public class Address {private String address;// ... getter, setter, toString
}// 学生类,包含各种类型的属性
public class Student {private String name; // 普通字符串private Address address; // 其他 Bean 对象private String[] books; // 数组private List<String> hobbies; // List 集合private Map<String, String> card; // Map 集合private Set<String> games; // Set 集合private Properties info; // Propertiesprivate String wife; // 用于测试 null// ... 所有属性的 getter, setter, toString
}
XML 配置大全
<beans><!-- 先配置一个 Address Bean --><bean id="address" class="com.github.subei.pojo.Address"><property name="address" value="成都市武侯区"/></bean><bean id="student" class="com.github.subei.pojo.Student"><!-- 1. 普通值/字面量注入:使用 value 属性 --><property name="name" value="小明"/><!-- 2. Bean 注入 (引用另一个Bean):使用 ref 属性 --><property name="address" ref="address"/><!-- 3. 数组注入:使用 <array> 和 <value> --><property name="books"><array><value>《Java核心技术》</value><value>《深入理解JVM》</value></array></property><!-- 4. List 注入:使用 <list> 和 <value> --><property name="hobbies"><list><value>编程</value><value>游戏</value></list></property><!-- 5. Map 注入:使用 <map> 和 <entry> --><property name="card"><map><entry key="身份证" value="510..."/><entry key="银行卡" value="622..."/></map></property><!-- 6. Set 注入:使用 <set> 和 <value> --><property name="games"><set><value>LOL</value><value>CS:GO</value></set></property><!-- 7. Properties 注入:使用 <props> 和 <prop> --><property name="info"><props><prop key="学号">2023001</prop><prop key="性别">男</prop></props></property><!-- 8. null 值注入:使用 <null/> --><property name="wife"><null/></property></bean>
</beans>
6.3 拓展方式注入 (p-命名空间和c-命名空间)
为了简化 XML 配置,Spring 提供了 p
和 c
命名空间,它们是 <property>
和 <constructor-arg>
的简写形式。
使用前准备:必须在 <beans>
根标签中引入对应的命名空间。
<?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:p="http://www.springframework.org/schema/p" <!-- 引入p命名空间 -->xmlns:c="http://www.springframework.org/schema/c" <!-- 引入c命名空间 -->xsi:schemaLocation="..."><!-- p-命名空间注入 (本质是 set 注入) --><!-- p:属性名="值" p:引用属性名-ref="引用的beanId" --><bean id="user" class="com.github.subei.pojo.User" p:name="subei" p:age="21"/><!-- c-命名空间注入 (本质是构造器注入, 需要有对应的有参构造) --><!-- c:构造器参数名="值" --><bean id="user2" class="com.github.subei.pojo.User" c:name="subei" c:age="22"/></beans>
6.4 Bean 的作用域 (scope
)
在Spring中,那些组成应用程序的主体及由Spring IoC容器所管理的对象,被称之为bean。简单地讲,bean就是由IoC容器初始化、装配及管理的对象。
Bean 的作用域定义了 Spring 容器如何创建和管理 Bean 实例的生命周期。通过在 <bean>
标签中设置 scope
属性来指定。
Scope | 描述 |
---|---|
singleton | (默认) 在整个 Spring IoC 容器中,该 Bean 只有一个实例。 |
prototype | 每次请求(如 getBean() )时,都会创建一个新的 Bean 实例。 |
request | 每次 HTTP 请求,创建一个新实例。仅在 Web 应用中有效。 |
session | 每个 HTTP Session,创建一个新实例。仅在 Web 应用中有效。 |
application | 在 ServletContext 的生命周期内,只创建一个实例。仅在 Web 应用中有效。 |
websocket | 在 websocket 的生命周期内,只创建一个实例。仅在 Web 应用中有效。 |
1. Singleton (单例模式)
这是 Spring 的默认行为。容器启动时就创建好实例,以后每次获取都是同一个。
<bean id="user" class="com.github.subei.pojo.User" scope="singleton"/>
测试:
User user1 = context.getBean("user", User.class);
User user2 = context.getBean("user", User.class);
System.out.println(user1 == user2); // 输出: true
2. Prototype (原型模式)
每次从容器获取时,都会创建一个全新的对象。适用于有状态的 Bean。
<bean id="user" class="com.github.subei.pojo.User" scope="prototype"/>
测试:
User user1 = context.getBean("user", User.class);
User user2 = context.getBean("user", User.class);
System.out.println(user1 == user2); // 输出: false
3. Request
Spring 容器通过为每个 HTTP 请求使用loginAction bean 定义来创建LoginAction bean 的新实例。也就是说,loginAction bean 的作用域是 HTTP 请求级别。您可以根据需要更改创建实例的内部状态,因为从同一loginAction bean 定义创建的其他实例看不到这些状态更改。它们特定于单个请求。当请求完成处理时,将限制作用于该请求的 Bean。
考虑以下 XML 配置来定义 bean :
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
使用注解驱动的组件或 Java 配置时,可以使用@RequestScope注解 将组件分配给request范围。以下示例显示了如何执行此操作:
@RequestScope
@Component
public class LoginAction {// ...
}
4、Section
当一个bean的作用域为Session,表示在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
考虑下面bean定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
在单个 HTTP Session的生存期内,Spring 容器通过使用userPreferences bean 定义来创建UserPreferences bean 的新实例。换句话说,userPreferences bean 的作用域实际上是 HTTP Session级别。与请求范围的 Bean 一样,您可以根据需要任意更改所创建实例的内部状态,因为知道其他 HTTP Session实例(也使用从相同userPreferences Bean 定义创建的实例)不会看到这些状态更改,因为它们特定于单个 HTTP Session。当最终丢弃 HTTP Session时,也将丢弃作用于该特定 HTTP Session的 bean。
@SessionScope
@Component
public class UserPreferences {// ...
}
参考
【狂神说Java】Spring5最新完整教程IDEA版通俗易懂
Spring学习目录(6天) - subeiLY - 博客园