Spring 中完整的 Scope 选项详解
Spring 的 scope 属性确实不止 singleton 和 prototype 两个。根据应用类型和 Spring 生态的不同,一共有 8 种可选值。它们可以分为三类:核心作用域、Web 作用域、扩展作用域。
一、核心作用域(2 个)
这两个是 Spring 容器内置的基础作用域,适用于所有类型的应用。
| 作用域 | 说明 |
|---|---|
singleton |
默认值,容器中只有一个实例,全局共享 |
prototype |
每次获取都创建新实例 |
java
@Component
@Scope("singleton") // 默认,可省略
public class UserService { }
@Component
@Scope("prototype")
public class ShoppingCart { }
二、Web 作用域(4 个)
这 4 个作用域只在 Web 应用中有效,需要 WebApplicationContext 支持。
1. request
每个 HTTP 请求创建一个实例,请求结束后销毁。
java
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
private String requestId;
// ...
}
2. session
每个 HTTP Session 创建一个实例,Session 失效时销毁。
java
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserSession {
private User currentUser;
// ...
}
3. application
每个 ServletContext 创建一个实例,整个 Web 应用共享。
java
@Component
@Scope(value = "application", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class AppCounter {
private AtomicInteger totalVisits = new AtomicInteger(0);
// ...
}
4. websocket
每个 WebSocket 会话创建一个实例,连接关闭时销毁。Spring 5+ 支持。
java
@Component
@Scope(value = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class WebSocketSessionBean {
private String sessionId;
// ...
}
三、扩展作用域(2 个)
这两个不在 Spring Framework 核心中,而是来自 Spring Cloud 或自定义实现。
5. refresh(Spring Cloud)
用于 Spring Cloud 的配置动态刷新。当配置中心(如 Nacos、Config Server)的配置变更时,被 @RefreshScope 标注的 Bean 会被重新创建,注入最新的配置值。
java
@Component
@RefreshScope // 等价于 @Scope("refresh")
public class ConfigurableBean {
@Value("${app.timeout}")
private int timeout;
}
当调用 /actuator/refresh 端点时,refresh 作用域的 Bean 会被销毁并重新创建,timeout 会更新为最新值。
6. globalSession(旧版 Portlet)
用于 Portlet 应用的全局会话作用域。在 Portlet 规范中,globalSession 表示跨所有 Portlet 的全局会话。Spring MVC 应用不使用这个作用域,它在 Spring 5 之后已经很少见。
xml
<bean id="globalSessionBean" class="com.example.MyBean" scope="globalSession"/>
四、自定义作用域
除了以上 6 个内置作用域,Spring 还允许通过实现 Scope 接口定义自定义作用域。常见的自定义作用域有:
7. thread(线程作用域)
每个线程一个实例,常用于线程隔离的场景。
java
public class ThreadScope implements Scope {
private final ThreadLocal<Map<String, Object>> threadScope =
ThreadLocal.withInitial(HashMap::new);
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
return threadScope.get().computeIfAbsent(name, k -> objectFactory.getObject());
}
@Override
public Object remove(String name) {
return threadScope.get().remove(name);
}
@Override
public void registerDestructionCallback(String name, Runnable callback) {
}
@Override
public Object resolveContextualObject(String key) {
return null;
}
@Override
public String getConversationId() {
return Thread.currentThread().getName();
}
}
注册:
java
@Configuration
public class ScopeConfig {
@Bean
public CustomScopeConfigurer customScopeConfigurer() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.addScope("thread", new ThreadScope());
return configurer;
}
}
使用:
java
@Component
@Scope("thread")
public class ThreadScopedBean {
}
8. 其他自定义作用域
根据业务需要,还可以定义更多作用域,例如:
- tenant:多租户场景,每个租户一个实例
- batch:批处理任务,每次批处理一个实例
- conversation:Web 对话作用域(类似 Seam 框架的 conversation)
五、8 个 Scope 汇总表
| 序号 | 作用域 | 来源 | 实例范围 | 适用场景 |
|---|---|---|---|---|
| 1 | singleton |
Spring 核心 | 整个容器一个 | 无状态服务 |
| 2 | prototype |
Spring 核心 | 每次获取新实例 | 有状态对象 |
| 3 | request |
Spring Web | 每个 HTTP 请求一个 | 请求级数据 |
| 4 | session |
Spring Web | 每个 Session 一个 | 用户会话数据 |
| 5 | application |
Spring Web | 每个 ServletContext 一个 | 全局共享数据 |
| 6 | websocket |
Spring WebSocket | 每个 WebSocket 连接一个 | WebSocket 会话 |
| 7 | refresh |
Spring Cloud | 配置刷新时重建 | 动态配置 |
| 8 | globalSession |
Portlet | 全局会话一个 | Portlet 应用(旧版) |
六、作用域的选择依据
| 场景 | 推荐作用域 | 理由 |
|---|---|---|
| Service、Dao、Controller | singleton |
无状态,共享安全 |
| 有状态的对象 | prototype |
隔离状态 |
| 请求上下文 | request |
每个请求独立 |
| 用户登录信息 | session |
每个用户独立 |
| 全局计数器 | application |
全应用共享 |
| WebSocket 会话 | websocket |
每个连接独立 |
| 动态配置 | refresh |
配置变更时刷新 |
| 线程隔离 | 自定义 thread |
每个线程独立 |
七、注意事项
1. Web 作用域需要代理
request、session、application、websocket 作用域的 Bean 注入到 singleton Bean 时,必须配置 proxyMode,否则会报 Scope 'xxx' is not active 错误。
2. 作用域名称大小写
Spring 的作用域名称是区分大小写 的。singleton 不能写成 Singleton,prototype 不能写成 Prototype。
3. 自定义作用域需要注册
自定义作用域必须先通过 CustomScopeConfigurer 注册到容器,才能在 @Scope 中使用。
4. prototype 的销毁不归 Spring 管
Spring 只创建 prototype Bean,不管理销毁。@PreDestroy 和 DisposableBean 对 prototype Bean 不生效。
5. refresh 依赖 Spring Cloud
refresh 作用域不是 Spring Framework 自带的,需要引入 Spring Cloud Context 依赖。
八、总结
| 分类 | 作用域 | 数量 |
|---|---|---|
| 核心作用域 | singleton、prototype |
2 |
| Web 作用域 | request、session、application、websocket |
4 |
| 扩展作用域 | refresh、globalSession |
2 |
| 自定义作用域 | thread、tenant 等 |
不限 |
Spring 一共提供了 8 种可选的作用域 (6 个内置 + 2 个扩展),另外还支持自定义。日常开发中最常用的是 singleton 和 prototype,Web 项目中会用到 request 和 session,Spring Cloud 项目中会用到 refresh。理解每种作用域的实例范围和生命周期,是设计高质量 Spring 应用的基础。