Spring Bean 一共有几种作用域?
- 标签:后端、Spring
🏛 业务架构师
Spring Bean 的作用域有两类:
基础作用域:
- singleton(默认):容器内一个实例。业务上绝大多数 Service、Repository、组件都是它——无状态 + 单例,是吞吐和内存效率的最优解;
- prototype:每次获取都新建。适合有状态、一次性的对象,比如每个请求独立的业务上下文对象。
Web 作用域(需在 Web 环境):
- request:一次 HTTP 请求一个实例;
- session:一次会话一个实例;
- application:整个 ServletContext 一个实例;
- websocket:一次 WebSocket 会话一个实例。
架构师视角的重点是风险与心智成本:
- 默认 singleton 意味着所有请求共享实例,一旦往里塞可变成员变量就是并发事故。我团队的铁律:单例 Bean 无状态,需要请求级状态就用
ThreadLocal、方法参数传递,或把状态对象显式声明为 prototype/request 作用域; - prototype Bean 被单例 Bean 注入时只注入一次(生命周期不托管销毁),这是个经典陷阱,必要时要配
ObjectProvider按需获取; - 微服务时代 session 作用域基本被淘汰——无状态服务 + Redis/JWT 管会话才是主流,这个趋势本身就是业务演进(弹性扩缩容)倒逼架构选型的例子。
一句话:作用域选错不报错、只出数据错乱事故,review 时必须重点盯。
🏗 系统架构师
六个:singleton(默认)、prototype、request、session、application、websocket(后四个需要 Web 上下文)。
架构师怎么用这个知识点:
- 默认 singleton 是水平扩展的前提:无状态服务层加状态外置(Redis/DB),实例才能随流量线性伸缩。我会审查团队代码里 singleton Bean 的成员变量——任何一个可变字段都是集群行为不一致的隐患;
- prototype 是反模式信号:一旦「每次注入都要新实例」,通常说明把状态对象当成了服务依赖。正确做法是把状态移出 Bean,而不是换作用域;
- session 作用域加集群等于粘性路由或分布式会话:要么 LB 做 session 保持(牺牲均衡与故障转移),要么用 Spring Session 把会话外置。我的默认选择是后者,甚至直接 JWT 无会话化;
- 把短周期 Bean 注入长周期 Bean,需要
ObjectProvider或代理模式(proxyMode),这是 scope 注入的经典坑。
💻 全栈开发专家
作用域共五种(Web 环境下才齐):
- singleton:默认,容器一个实例
- prototype:每次 getBean 新实例,容器不负责销毁回调
- request/session/application/websocket:Web 环境专用,request 每次请求一个、session 每会话一个
java
@Component
@Scope("prototype")
public class TaskWorker { }最经典的坑:singleton 里注入 prototype 只注入一次——单例任务执行器里注入 prototype 上下文,跑起来发现永远同一个实例。解法:注入 ObjectProvider<T> 每次 getObject(),或用 @Lookup 方法注入。
另一个坑:session 作用域 Bean 被单例引用时要加代理模式 @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS),否则启动报"非单例依赖单例"错误——原理是注入一个代理,每次调用路由到当前 session 的真实实例。
注意 WebFlux 没有 request/session 作用域(无 Servlet 体系),迁移时容易忽略。实际业务 95% 都是 singleton,出并发问题先查单例里的可变成员变量。