Spring Bean作用域与代理模式
本文讲解Spring Bean的核心作用域:
singleton(默认单例)、prototype(原型),及Web环境专用的request和session。作用域控制着Bean实例的生命周期与共享范围,是应用设计的关键。
Spring Bean 的作用域(Scope)决定了 Spring 容器如何创建和管理 Bean 实例。正确选择作用域对于应用的性能、内存使用和线程安全至关重要。
Spring 框架内置了以下几种核心作用域:
1. Singleton (单例)
- 定义:这是 Spring 的默认作用域。在整个 Spring IoC 容器中,一个 Bean 定义只对应一个实例。
- 生命周期:该实例在容器启动时创建(默认情况下),并在容器关闭时销毁。
- 适用场景:适用于无状态的 Bean,如 Service、Repository、Controller、配置类等。它们不保存每次请求的数据,只提供方法。
- 注意事项:由于是共享实例,如果 Bean 内部有可变的状态(成员变量),需要特别注意线程安全问题。
java
@Component // 默认就是 @Scope("singleton")
public class UserService {
// ...
}
2. Prototype (原型)
- 定义:每次向容器请求(
getBean())该 Bean 时,都会创建一个全新的实例。 - 生命周期:Spring 容器只负责创建、配置和初始化该 Bean 实例,然后就将其交给请求方,不再管理其后续的生命周期。容器不会调用其销毁回调方法(如
@PreDestroy)。 - 适用场景:适用于有状态的 Bean,即那些需要保存独立状态的对象。例如,一个代表用户操作的 Action 对象。
- 注意事项:频繁创建和销毁可能会带来性能开销。
java
@Component
@Scope("prototype")
public class UserAction {
private String data; // 每个 Action 实例都有自己的数据
// ...
}
以下作用域仅在 Web 应用环境下有效
这些作用域需要一个支持 Web 的 ApplicationContext(如 XmlWebApplicationContext 或 AnnotationConfigWebApplicationContext)。
3. Request (请求)
- 定义:为每一次 HTTP 请求创建一个独立的 Bean 实例。
- 生命周期:实例在 HTTP 请求开始时创建,在请求结束时销毁。
- 适用场景:需要在单次请求内共享的数据。例如,一个持有当前请求用户信息的对象。
java
@Component
@Scope("request")
// 或者使用更具体的注解: @RequestScope
public class RequestDataHolder {
// ...
}
4. Session (会话)
- 定义:为每一个 HTTP Session创建一个独立的 Bean 实例。
- 生命周期:实例在用户首次创建 Session 时创建,在 Session 失效(如超时或用户登出)时销毁。
- 适用场景:需要在整个用户会话期间共享的数据。最经典的例子就是购物车。
java
@Component
@Scope("session")
// 或者使用更具体的注解: @SessionScope
public class ShoppingCart {
// ...
}
5. Application (应用)
- 定义:在整个
ServletContext的生命周期内,只创建一个 Bean 实例。它类似于singleton,但作用范围是ServletContext。 - 生命周期:实例在 Web 应用启动时创建,在 Web 应用关闭时销毁。
- 适用场景:应用的全局配置信息、共享的计数器等。
java
@Component
@Scope("application")
// 或者使用更具体的注解: @ApplicationScope
public class AppConfig {
// ...
}
scoped-proxy(作用域代理)
一个常见的问题是:如何将一个短生命周期的 Bean(如 request 或 session 作用域)注入到一个长生命周期的 Bean(如 singleton 作用域的 Controller)中?
直接注入会出问题,因为 singleton Bean 只创建一次,它会在启动时就尝试注入一个当时还不存在的 request Bean。
解决方法是使用作用域代理。Spring 会注入一个代理对象,这个代理对象会在每次方法调用时,从当前正确的范围(如当前请求或当前会话)中获取真实的 Bean 实例来执行操作。
java
import org.springframework.web.context.annotation.RequestScope;
import org.springframework.stereotype.Controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ScopedProxyMode;
@Controller // Singleton scope by default
public class MyController {
@Autowired
private RequestDataHolder requestDataHolder; // 注入的是一个代理
public void handleRequest() {
// 每次调用,代理都会从当前 HTTP 请求中获取真正的 RequestDataHolder 实例
requestDataHolder.process();
}
}
@Component
@RequestScope(proxyMode = ScopedProxyMode.TARGET_CLASS) // 关键点!
public class RequestDataHolder {
public void process() {
// ...
}
}
总结表格
| 作用域 (Scope) | 描述 | 适用环境 |
|---|---|---|
singleton |
(默认) 整个容器共享一个实例。 | 所有环境 |
prototype |
每次请求都创建一个新实例。容器不管理其销毁。 | 所有环境 |
request |
每次 HTTP 请求创建一个新实例。 | 仅 Web 环境 |
session |
每个 HTTP Session 创建一个新实例。 | 仅 Web 环境 |
application |
每个 ServletContext 创建一个新实例。 |
仅 Web 环境 |
websocket |
每个 WebSocket 会话创建一个实例。 | 仅 WebSocket 环境 |
右滑查看面试常问