Spring 中 Bean 的作用域有哪些?

一则或许对你有用的小广告

欢迎 加入小哈的星球 ,你将获得: 专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目: 《Spring AI 项目实战(问答机器人、RAG 增强检索、联网搜索)》 正在持续爆肝中,基于 Spring AI + Spring Boot3.x + JDK 21...点击查看;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot3.x + JDK 17...点击查看项目介绍; 演示链接: http://116.62.199.48:7070/;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/

面试考察点

面试官提出这个问题,通常想考察以下几个层面的理解:

  1. 对 Spring IoC 容器基础概念的掌握程度: 是否理解 Bean 是 Spring 管理的核心对象,以及 “作用域” 决定了 Bean 的生命周期和可见范围。
  2. 对不同应用场景下 Bean 管理的认知: 不仅仅是记住有哪些作用域,更是想知道你是否理解在 单线程、多线程、Web 环境 等不同场景下,应该如何选择合适的作用域来保证程序的正确性、性能和数据隔离性。
  3. 对线程安全问题的敏感度: 例如,你是否清楚 singleton 作用域的 Bean 在并发访问时可能存在的线程风险,以及如何规避。
  4. 对 Spring 容器工作机制的理解深度: 例如,prototype 作用域 Bean 的生命周期管理责任由谁承担,request 作用域在 Web 环境中的具体创建和销毁时机等。

核心答案

Spring Framework 为 Bean 定义了多种作用域,你可以通过 @Scope 注解或 XML 配置来指定。在标准 Spring 容器中,主要有以下五种核心作用域:

  1. singleton (单例,默认作用域): 在整个 Spring IoC 容器中,只存在一个该 Bean 的实例。所有对该 Bean 的依赖注入和查找,返回的都是同一个对象。
  2. prototype (原型): 每次通过容器获取该 Bean 时,都会创建一个新的实例。
  3. request: 在 Web 应用中,为每一个 HTTP 请求创建一个新的 Bean 实例。该实例仅在当前 HTTP request 生命周期内有效。
  4. session: 在 Web 应用中,为每一个 HTTP Session 创建一个新的 Bean 实例。该实例在整个用户会话期间有效。
  5. application: 在 Web 应用中,为整个 ServletContext 创建一个 Bean 实例。这可以理解为 ServletContext 级别的单例,其生命周期与 ServletContext 绑定。

注意request, session, application 作用域仅在具有 Web 感知的 Spring 容器(如 WebApplicationContext)中才有效,在纯 Spring 控制台应用中会报错。

深度解析

原理与机制

作用域的本质是 Spring 容器管理 Bean 实例创建和存储的策略

  • singleton: Bean 定义被加载后,容器就创建唯一实例并缓存到单例 Bean 注册表中。后续所有请求都返回该缓存引用。其生命周期与容器几乎相同
  • prototype: 容器在每次请求(如 getBean() 或依赖注入)时,都执行 new 关键字创建新对象并返回。之后,容器便不再管理该实例的生命周期(即不负责其销毁)。对象的销毁需要由客户端代码或 GC 负责
  • request/session/application: 在 Web 环境中,Spring 通过 RequestContextListenerDispatcherServlet 等机制,将 HTTP 请求/会话/应用上下文与当前线程绑定。容器从特定的作用域(如 HttpServletRequest 属性)中获取或创建 Bean。生命周期由对应的 Web 容器管理。

代码示例

// 1. 使用 @Scope 注解配置(最常用)
@Component
@Scope("singleton") // 或 @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class SingletonService {
    // ...
}

@Component
@Scope("prototype") // 或 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeService {
    // ...
}

@Controller
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserPreferenceController {
    // 注意:当将 request/session 作用域的 Bean 注入到更长生命周期的 singleton Bean 时,
    // 需要使用代理(如 TARGET_CLASS)。这里 proxyMode 是关键。
    @Autowired
    private UserPreferences userPreferences; // 假设是 @Scope(“request”) 的 Bean
}

// 2. XML 配置方式(了解即可)
<bean id="accountService" class="com.example.AccountServiceImpl" scope="prototype"/>

最佳实践与注意事项

  1. 默认使用 singleton: 无状态的服务层(Service)、数据访问层(DAO)、工具类 Bean 应优先使用 singleton,以减少对象创建开销,这也是 Spring 的默认选择。
  2. 有状态 Bean 需谨慎: 如果一个 Bean 含有可变的成员变量(状态),并且会被多线程访问,将其设为 singleton 极有可能导致线程安全问题。此时,应考虑使用 prototyperequest 或将状态变量(如 SimpleDateFormat)改为 ThreadLocal 存储。
  3. Web 作用域的使用场景
    • request: 存储当前请求的用户身份信息、表单数据等。
    • session: 存储用户登录信息、购物车内容等。
    • application: 存储应用级别的缓存、全局配置等。
  4. 原型模式的代价: 过度使用 prototype 会增加 GC 压力,因为会产生大量短生命周期对象。仅在确实需要每次新实例时才使用。

常见误区

  1. 误以为 prototype 能解决所有 singleton 的并发问题: 如果 prototype Bean 本身被设计为有状态的,且其引用被多个线程共享,它依然存在线程安全问题。prototype 解决的是“实例隔离”,而非 “线程安全”。
  2. 混淆容器生命周期与对象生命周期: 认为容器关闭时会自动销毁所有 prototype Bean。实际上,Spring 只负责创建,不负责销毁其 prototype Bean。
  3. 在非 Web 环境使用 Web 作用域: 这会导致 BeanCreationException
  4. 忽略代理模式(proxyMode): 当将短生命周期作用域的 Bean(如 request)注入到长生命周期的 Bean(如 singleton Controller)时,必须在 @Scope 中设置 proxyMode(如 ScopedProxyMode.TARGET_CLASS)。因为注入发生在 singleton Bean 初始化时,此时 request 可能不存在。设置代理后,Spring 会注入一个代理对象,每次方法调用时代理会去当前作用域获取真实 Bean。

总结

Spring Bean 的五大作用域(singleton, prototype, request, session, application)是 IoC 容器管理对象粒度和生命周期的基础策略,核心选择依据是业务场景对实例数量、生命周期和数据隔离性的需求,正确的选择直接关系到应用的正确性、性能和资源管理