SpringBoot中的Controller是单例吗

内容纲要

是的,SpringBoot 中的 @Controller(以及 @RestController)默认是 单例(Singleton) 的。

原因在于:

  1. Spring Bean 默认作用域

    • Spring 容器管理的所有 Bean 默认作用域是 singleton
    • @Controller 只是一个特殊的 Bean 注解,本质上和 @Component 一样,默认作用域同样是单例。
  2. 请求处理的线程安全问题

    • 每次请求并不会创建新的 Controller 实例,而是由多个线程共享同一个 Controller 对象。
    • 这意味着 不要在 Controller 中定义有状态的成员变量(例如:可变的 List、Map、计数器等),否则在并发访问时可能会发生线程安全问题。
    • 正确做法是:把状态交给方法参数、本地变量,或者交给 Service / Repository 层处理。
  3. 如何修改作用域
    如果确实有需求让 Controller 不是单例,可以用 @Scope 修改,比如:

    @Controller
    @Scope("prototype") // 每次请求都会创建一个新的实例
    public class MyController {
    }

    但这种情况比较少见,因为 Spring MVC 的设计理念就是 Controller 是无状态的,单例即可。


👉 总结:

  • 默认单例,所有请求共享同一个 Controller 实例。
  • 注意线程安全,避免在 Controller 中定义有状态成员变量。
  • 特殊需求可以用 @Scope("prototype") 改成多例。

🌐 SpringMVC 请求到 Controller 的流程(简化版)

这张图会帮你更直观理解为什么是单例

sequenceDiagram
    participant Browser as 浏览器/客户端
    participant Dispatcher as DispatcherServlet
    participant HandlerMapping as HandlerMapping
    participant Controller as @Controller (单例Bean)
    participant Service as Service层
    participant View as ViewResolver

    Browser->>Dispatcher: 发送HTTP请求
    Dispatcher->>HandlerMapping: 查找对应的Controller方法
    HandlerMapping->>Dispatcher: 返回Handler(Controller+Method)
    Dispatcher->>Controller: 调用Controller方法 (同一个单例对象)
    Controller->>Service: 调用业务逻辑
    Service-->>Controller: 返回结果数据
    Controller-->>Dispatcher: 返回ModelAndView/ResponseBody
    Dispatcher->>View: 渲染视图
    View-->>Browser: 返回HTTP响应

↑↑↑Spring MVC 请求到 Controller 的生命周期示意图

🔑 关键点说明

  1. Controller 是单例

    • DispatcherServlet 每次请求过来时,会根据 HandlerMapping 找到已经注册好的 Controller Bean(单例),然后调用它的方法。
    • 不是每个请求都 new 一个 Controller。
  2. 线程安全风险

    • 因为同一个 Controller 实例会被多个线程调用,如果你在类里定义了共享的可变成员变量,可能会造成数据混乱。
    • 正确做法:方法内使用局部变量,或者把状态托管到 ServiceDBRedis 等。
  3. 生命周期对比

    • 单例 Controller:应用启动时就实例化,整个应用共享。
    • 原型 Controller (@Scope("prototype")):每次注入或请求都会新建实例,但几乎没人这样用,会有性能和管理成本。

Controller 单例 vs 多例 对比表


📊 SpringBoot Controller 作用域对比

特性 默认单例(@Controller 多例(@Controller @Scope("prototype")
生命周期 Spring 容器启动时创建一个实例,整个应用中共享 每次注入 / 调用都会新建一个实例
内存开销 较低(只保留一个实例) 较高(频繁创建和销毁对象)
线程安全 ⚠️ 有风险:多个线程共享同一个对象,若存在成员变量则可能冲突 ✅ 安全:每个请求独立实例,不会共享成员变量
常见使用场景 绝大多数情况(Controller 方法无状态) 少见:需要在 Controller 内维护独立的会话级 / 请求级状态时
性能 高效(单例复用) 低效(频繁创建销毁)
推荐实践 保持 Controller 无状态,把状态交给方法参数、Service、数据库 尽量避免;如果需要请求隔离,更推荐 @Scope("request")

💡 结论

  • 绝大部分业务场景下,单例足够且推荐
  • 如果确实要存储和隔离请求状态,可以考虑:

    1. 使用 @Scope("request"),保证每个 HTTP 请求独立一个 Controller 实例。
    2. 使用 ThreadLocal 保存线程级变量(例如登录用户信息)。
    3. 将有状态的数据下放到 Service / Session / Redis,而不是放在 Controller 成员变量里。

Leave a Comment

您的电子邮箱地址不会被公开。 必填项已用*标注

close
arrow_upward