基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

Spring Bean作用域与代理模式

知识点图片

本文讲解Spring Bean的核心作用域:singleton(默认单例)、prototype(原型),及Web环境专用的requestsession。作用域控制着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(如 XmlWebApplicationContextAnnotationConfigWebApplicationContext)。

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(如 requestsession 作用域)注入到一个长生命周期的 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 环境
00:00
00:00