Spring Bean 作用域(Scope)全解析:从单例到 Web 专用作用域
Spring Bean 的作用域(Scope) 定义了 Bean 在 Spring 容器中的生命周期范围与实例创建规则,直接影响 Bean 的复用性、线程安全性和资源占用。Spring 提供 5 种核心作用域,涵盖普通 Java 应用(单例 / 原型)和 Web 应用(请求 / 会话 / 应用级),本文将详细拆解每种作用域的特性、适用场景、底层实现及注意事项。
Spring Bean 作用域总览
Spring 容器中 Bean 的 5 种作用域,按适用场景可分为普通应用作用域(适用于所有环境)和Web 专用作用域(仅适用于 Spring Web 环境,如 Spring MVC):
| 作用域名称 | 英文标识 | 核心特性 | 适用场景 | 生效环境 |
|---|---|---|---|---|
| 单例 | singleton |
容器中仅存在1 个实例,随容器启动创建(默认懒加载可配置),全局共享 | 无状态组件(如 Service、Dao、工具类) | 所有环境(默认) |
| 原型 | prototype |
每次调用 getBean() 或注入时创建新实例,容器不管理销毁 |
有状态组件(如 Command 对象、请求参数封装类) | 所有环境 |
| 请求 | request |
每个 HTTP 请求对应1 个实例,请求结束后销毁 | 存储请求级数据(如当前请求的用户信息) | Web 环境(Spring Web) |
| 会话 | session |
每个 HTTP Session 对应1 个实例,会话过期后销毁 | 存储会话级数据(如用户登录状态、购物车) | Web 环境(Spring Web) |
| 应用 / 全局会话 | application |
整个 ServletContext 对应1 个实例,与应用生命周期一致 | 存储应用级全局数据(如系统配置、字典缓存) | Web 环境(Spring Web) |
注意:
globalSession是application的早期别名,仅在 Portlet 环境中有特殊含义,现代 Web 开发(Servlet 环境)中统一使用application。
各作用域详解与实战
1. 单例(singleton):默认作用域
核心特性
- 实例数量:容器中唯一实例,所有请求共享同一个 Bean;
- 创建时机:默认在容器启动时创建(预初始化),可通过
@Lazy注解改为 “首次使用时创建”(懒加载); - 生命周期:与 Spring 容器一致(容器启动时创建,容器关闭时销毁);
- 线程安全:无状态时线程安全(如 Service 仅调用 Dao,无成员变量修改);有状态时线程不安全(如成员变量存储请求数据)。
配置方式
- XML 配置:
<bean id="userService" class="com.example.UserService" scope="singleton"/>(默认可省略scope); - 注解配置:
@Scope("singleton")(或@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON))。
示例(默认单例)
1 |
|
关键注意点
推荐无状态设计:单例 Bean 若需线程安全,应避免定义可修改的成员变量(如
count),或通过ThreadLocal隔离线程状态;懒加载配置:通过@Lazy延迟创建(适合初始化耗时的 Bean,如缓存服务):
1
2
3
4
5
// 容器启动不创建,首次 getBean() 时创建
public class CacheService {
}
2. 原型(prototype):每次请求新实例
核心特性
- 实例数量:每次调用
getBean()、自动注入(@Autowired)或通过BeanFactory获取时,创建新实例; - 创建时机:懒加载(仅在获取时创建,容器启动不初始化);
- 生命周期:容器仅负责 “创建实例”,不管理 “销毁”(实例成为垃圾后由 JVM 回收,
@PreDestroy注解无效); - 线程安全:每个线程 / 请求获取独立实例,天然线程安全(状态不共享)。
配置方式
- XML 配置:
<bean id="user" class="com.example.User" scope="prototype"/>; - 注解配置:
@Scope("prototype")(或@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE))。
示例(原型 Bean)
1 |
|
关键注意点
销毁逻辑需手动处理:若原型 Bean 持有资源(如数据库连接、文件流),需在业务代码中手动关闭(
@PreDestroy不生效);注入方式影响实例数量:若在单例 Bean 中注入原型 Bean,仅在单例 Bean 初始化时创建一次原型实例(后续复用),需通过
ObjectFactory动态获取新实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class OrderService {
// 错误:单例 Bean 注入原型 Bean,仅创建一次
private User user;
// 正确:通过 ObjectFactory 动态获取新实例
private ObjectFactory<User> userFactory;
public void createOrder() {
User newUser = userFactory.getObject(); // 每次调用获取新实例
}
}
3. Web 专用作用域(request/session/application)
Web 专用作用域仅在 Spring Web 环境(如 Spring MVC、Spring Boot Web)中生效,依赖 WebApplicationContext 容器,核心是将 Bean 实例与 Web 容器的生命周期(请求、会话、应用)绑定。
(1)请求作用域(request):每个请求一个实例
- 核心特性:每个 HTTP 请求对应 1 个 Bean 实例,请求处理完成后,Bean 被销毁(随请求生命周期结束);
- 适用场景:存储请求级临时数据(如请求参数封装、当前请求的用户 IP);
- 配置方式:
@Scope("request")(或@Scope(WebApplicationContext.SCOPE_REQUEST))。
示例(request 作用域)
1 |
|
(2)会话作用域(session):每个会话一个实例
- 核心特性:每个 HTTP Session 对应 1 个 Bean 实例,会话过期(如用户退出、超时)后,Bean 被销毁;
- 适用场景:存储会话级数据(如用户登录信息、购物车内容);
- 配置方式:
@Scope("session")(或@Scope(WebApplicationContext.SCOPE_SESSION))。
示例(session 作用域)
1 |
|
(3)应用作用域(application):全局应用一个实例
- 核心特性:整个 Web 应用(
ServletContext)对应 1 个 Bean 实例,与应用生命周期一致(应用启动时创建,关闭时销毁); - 与 singleton 区别:
singleton是 Spring 容器级单例,若多个WebApplicationContext共存(如多模块部署),会有多个singleton实例;application是ServletContext级单例,整个应用唯一; - 适用场景:存储应用级全局数据(如系统配置、字典缓存、计数器);
- 配置方式:
@Scope("application")(或@Scope(WebApplicationContext.SCOPE_APPLICATION))。
示例(application 作用域)
1 |
|
作用域底层实现原理
Spring 对不同作用域的 Bean 采用不同的实例管理策略,核心依赖 Scope 接口(定义作用域的实例获取与销毁逻辑):
1 | public interface Scope { |
各作用域对应 Scope 接口的实现类:
- singleton:
SingletonScope→ 实例存储在DefaultSingletonBeanRegistry的singletonObjects缓存中; - prototype:
PrototypeScope→ 每次调用get()时通过ObjectFactory创建新实例,不缓存; - request:
RequestScope→ 实例存储在HttpServletRequest的属性中(request.setAttribute()); - session:
SessionScope→ 实例存储在HttpSession的属性中(session.setAttribute()); - application:
ServletContextScope→ 实例存储在ServletContext的属性中(servletContext.setAttribute())。
作用域选择最佳实践
- 优先使用 singleton:无状态组件(Service、Dao、工具类)默认用 singleton,节省资源且复用性高;
- 原型用于有状态组件:如 Command 对象(封装单次请求的业务参数)、线程不安全的工具类(如 SimpleDateFormat);
- Web 作用域按需选择:
- 临时请求数据 → request;
- 用户会话数据 → session;
- 全局应用数据 → application;
- 避免滥用 session/application:session 存储过多数据会导致内存占用过高,application 需注意线程安全(多线程并发修改时需加锁)。
常见问题与避坑指南
- 原型 Bean 注入单例 Bean 后复用:
- 问题:单例 Bean 初始化时注入原型 Bean,后续调用复用同一实例;
- 解决方案:通过
ObjectFactory<User> userFactory或Provider<User>(JSR-330)动态获取新实例。
- Web 作用域在非 Web 环境生效:
- 问题:在普通 Java 应用(无
WebApplicationContext)中使用 request/session 作用域,启动报错; - 解决方案:确保仅在 Web 环境中使用 Web 专用作用域,或通过
@Profile("web")限制环境。
- 问题:在普通 Java 应用(无
- session 作用域线程安全:
- 问题:同一 session 可能被多个线程并发访问(如用户同时打开多个标签页);
- 解决方案:session Bean 避免定义可修改的成员变量,或通过
synchronized加锁。
总结
Spring Bean 作用域是控制 Bean 生命周期和实例管理的核心机制,不同作用域对应不同的使用场景:
- singleton 是默认选择,适合无状态组件;
- prototype 适合有状态、一次性使用的组件;
- Web 专用作用域(request/session/application)则绑定 Web 容器生命周期,解决 Web 开发中的数据隔离问题