在 Spring 框架中,Bean 的作用域定义了 Bean 实例的生命周期和可见范围。合理选择作用域可以优化应用性能、确保线程安全并正确管理资源。以下是对 Spring Bean 作用域的详细解析:
1. Spring 支持的 Bean 作用域类型
Spring 提供了多种作用域选项,其中最常用的是 Singleton 和 Prototype,其他作用域需在 Web 环境中使用。
| 作用域 | 描述 | 生命周期 |
|---|---|---|
| Singleton | (默认)每个 Spring IoC 容器中仅存在一个实例,所有请求返回同一实例。 | 容器启动时创建,容器关闭时销毁。 |
| Prototype | 每次请求都创建新的 Bean 实例。 | 创建后由调用者管理,Spring 不跟踪其销毁。 |
| Request | 每个 HTTP 请求创建一个实例(仅 Web 应用有效)。 | 请求开始时创建,请求结束时销毁。 |
| Session | 每个 HTTP 会话创建一个实例(仅 Web 应用有效)。 | 会话开始时创建,会话过期时销毁。 |
| Application | 每个 ServletContext 创建一个实例(仅 Web 应用有效)。 | 应用启动时创建,应用关闭时销毁。 |
| WebSocket | 每个 WebSocket 会话创建一个实例(仅 WebSocket 应用有效)。 | WebSocket 连接建立时创建,连接关闭时销毁。 |
2. 核心作用域详解
2.1 Singleton(单例)
- 特点:全局唯一实例,所有对该 Bean 的请求都返回同一对象。
- 适用场景:无状态 Bean(如 Service、Repository)。
- 风险:若 Bean 包含可变状态(成员变量),可能存在线程安全问题。
示例:
@Component // 默认是 Singleton
public class UserService {
// 无状态实现,线程安全
public void createUser(String username) {
// ...
}
}
@Component
public class MyService {
// 默认是单例作用域
}
@Autowired
private MyService service1;
@Autowired
private MyService service2;
System.out.println(service1 == service2); // 输出 true,证明是同一个实例
2.2 Prototype(原型)
- 特点:每次请求都创建新实例,适合有状态 Bean。
- 适用场景:需要保持会话状态的 Bean(如计数器、状态机)。
- 注意:Spring 不管理 Prototype Bean 的生命周期,需调用者手动销毁。
示例:
@Component
@Scope("prototype") // 或 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ShoppingCart {
private List<Product> items = new ArrayList<>(); // 有状态
public void addItem(Product product) {
items.add(product);
}
}
3. Web 环境专用作用域
3.1 Request(请求作用域)
- 特点:每个 HTTP 请求创建一个新 Bean,请求结束后销毁。
- 应用场景:封装请求相关数据(如请求参数、用户信息)。
示例:
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestInfo {
private String requestId;
public RequestInfo() {
this.requestId = UUID.randomUUID().toString();
}
public String getRequestId() {
return requestId;
}
}
3.2 Session(会话作用域)
- 特点:每个用户会话创建一个 Bean,会话过期后销毁。
- 应用场景:存储用户会话数据(如购物车、登录状态)。
示例:
@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserSession {
private User currentUser;
public User getCurrentUser() {
return currentUser;
}
public void setCurrentUser(User user) {
this.currentUser = user;
}
}
3.3 Application(应用作用域)
- 特点:每个 ServletContext 共享一个 Bean,等价于 ServletContext 的全局变量。
- 应用场景:存储全局应用数据(如应用配置、计数器)。
4. 代理模式(ProxyMode)
当使用 Request/Session 等作用域的 Bean 注入到 Singleton Bean 时,需通过代理模式解决作用域不匹配问题:
@Service
public class OrderService {
@Autowired
private UserSession userSession; // 会话作用域 Bean
public void createOrder() {
User user = userSession.getCurrentUser(); // 通过代理访问实际 Bean
// ...
}
}
- ScopedProxyMode.INTERFACES:基于接口的 JDK 动态代理(Bean 必须实现接口)。
- ScopedProxyMode.TARGET_CLASS:基于 CGLIB 的类代理(无需实现接口)。
5. 自定义作用域
通过实现 Scope 接口可自定义作用域,例如线程作用域:
public class ThreadScope implements Scope {
private final ThreadLocal<Map<String, Object>> threadScope =
ThreadLocal.withInitial(HashMap::new);
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String, Object> scope = threadScope.get();
return scope.computeIfAbsent(name, k -> objectFactory.getObject());
}
// 其他方法实现...
}
// 注册自定义作用域
@Configuration
public class AppConfig {
@Bean
public CustomScopeConfigurer customScopeConfigurer() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.addScope("thread", new ThreadScope());
return configurer;
}
}
6. 选择合适的作用域
- 优先使用 Singleton:无状态 Bean 默认使用单例,减少内存开销。
- 使用 Prototype:有状态 Bean 且需在多线程环境中独立使用。
- Web 作用域:仅在确实需要跟踪请求或会话状态时使用。
- 避免过度使用 Prototype:频繁创建对象会增加 GC 压力。
7. 常见问题与注意事项
- Singleton Bean 的线程安全:避免在 Singleton Bean 中存储可变状态。
- Prototype Bean 的注入:若需在 Singleton Bean 中使用 Prototype Bean,可通过
ObjectFactory或@Lookup方法动态获取。 - Web 作用域的依赖:Request/Session Bean 注入到 Singleton Bean 时必须使用代理模式。
- 生命周期管理:Prototype Bean 需手动管理销毁,其他作用域由 Spring 自动处理。
通过合理配置 Bean 的作用域,可有效提升应用性能和可维护性,避免常见的线程安全和内存泄漏问题。
526

被折叠的 条评论
为什么被折叠?



