原创

Spring 中的 @RestController 和 @Controller 详解与区别

一、为什么这个问题经常被讲错

在 Spring MVC 或 Spring Boot Web 开发中,@Controller@RestController 几乎是最常见的控制器注解,但也恰恰因为常见,很多文章会把两者的区别讲得过于简单,最后只剩下一句“一个返回页面,一个返回 JSON”。这句话不能说完全错,但明显不够准确。

真正严谨的理解方式应该是:

  • @Controller 表示当前类是一个 Spring MVC 控制器,参与请求分发与处理。
  • @RestController 本质上是一个组合注解,等价于 @Controller + @ResponseBody,它让当前控制器中的请求处理方法默认采用“将返回值直接写入 HTTP 响应体”的语义,而不是交给视图解析器进行页面渲染。这个结论来自 Spring 官方文档和 API 定义。
  • 因此,两者最核心的差异,不是“能不能返回 JSON”,而是方法返回值究竟被当作视图名处理,还是被当作响应体处理

如果只记住“@Controller 返回页面,@RestController 返回 JSON”,一旦遇到下面这些场景就容易出错:

  • @Controller 配合 @ResponseBody 一样可以返回 JSON
  • @RestController 也可以返回字符串,但这个字符串会作为响应体内容,而不是视图名称
  • 下载文件、返回二进制流、返回 ResponseEntity、返回错误对象、返回异步结果时,二者的表现差异并不只是“页面还是 JSON”这么简单
  • 前后端分离、服务端模板渲染、接口网关、统一异常处理、内容协商等场景下,二者选型直接影响系统设计

所以,这个主题如果要讲透,必须从 Spring MVC 的请求处理模型出发,而不是停留在“会不会返回 JSON”这一层。


二、先理解 Spring MVC 对控制器返回值的处理逻辑

Spring Web MVC 是基于 Servlet API 的传统 Web 框架,请求进入后通常会由 DispatcherServlet 分发给匹配到的处理器方法。Spring 官方文档明确说明,@Controller@RestController 都属于注解式控制器模型的一部分。

一个请求处理方法执行结束后,Spring 需要决定两件事:

  1. 这个返回值是不是“模型 + 视图”的一部分
  2. 这个返回值是不是应该经过 HttpMessageConverter 写入响应体

这正是 @Controller@RestController 差异产生的根源。

1. 视图渲染模式

如果控制器方法返回的是视图名,例如返回 "user/list",Spring 会继续走视图解析流程:

  • 查找 ViewResolver
  • 定位模板,例如 Thymeleaf、JSP、FreeMarker
  • Model 中的数据填充进页面
  • 渲染 HTML 返回浏览器

这类处理最典型的就是传统服务端页面应用。

2. 响应体写出模式

如果控制器方法被 @ResponseBody 标记,或者所在类本身带有 @RestController,那么返回值会交给 HttpMessageConverter 处理,常见行为包括:

  • Java 对象转 JSON
  • 字符串写入响应体
  • 字节数组写入响应体
  • ResponseEntity<T> 连同状态码、响应头、响应体一起输出

Spring 官方文档明确指出:@ResponseBody 可以放在方法级别,也可以放在类级别;而 @RestController 本质上就是类级别 @ResponseBody 的效果。citeturn0search1turn0search0

所以,控制器注解的选择,实际上是在定义控制器的默认返回值语义


三、@Controller 的本质与职责

1. 定义

@Controller 是 Spring MVC 中的标准控制器注解,用于将一个类标识为 Web 层处理组件。被它标注的类会被 Spring 识别为候选控制器,并参与请求映射。

它的核心职责包括:

  • 接收 HTTP 请求
  • 进行参数绑定
  • 调用业务层
  • 组装模型数据
  • 返回视图或响应结果

2. 典型使用方式

@Controller
@RequestMapping("/user")
public class UserController {

    @GetMapping("/list")
    public String list(Model model) {
        model.addAttribute("users", List.of("Tom", "Jack", "Lucy"));
        return "user/list";
    }
}

这里的返回值 "user/list" 会被认为是视图名,而不是响应体内容。

3. 在 @Controller 中返回 JSON

很多人误以为 @Controller 不能返回 JSON,这种说法是错误的。只要在方法上增加 @ResponseBody@Controller 同样可以直接输出 JSON。

@Controller
@RequestMapping("/api/user")
public class UserApiController {

    @GetMapping("/{id}")
    @ResponseBody
    public UserVO getById(@PathVariable Long id) {
        return new UserVO(id, "Tom");
    }
}

这里的 UserVO 不会被当作模型对象进入视图层,而是会被消息转换器序列化后写入响应体。

4. 适合 @Controller 的场景

@Controller 最适合下面几类项目:

4.1 服务端模板渲染

例如:

  • Thymeleaf
  • JSP
  • FreeMarker
  • Beetl

如果系统需要返回后台管理页面、运营平台页面、CMS 模板页面,那么 @Controller 是天然选择。

4.2 一个控制器中同时存在页面与接口逻辑

虽然从职责划分上更推荐拆开,但有些存量系统中一个控制器类既有页面跳转方法,也有 AJAX 接口方法,此时 @Controller + 部分方法 @ResponseBody 是比较常见的写法。

@Controller
@RequestMapping("/order")
public class OrderController {

    @GetMapping("/page")
    public String page() {
        return "order/page";
    }

    @GetMapping("/detail/{id}")
    @ResponseBody
    public OrderVO detail(@PathVariable Long id) {
        return new OrderVO(id, "PAID");
    }
}

4.3 传统单体 Web 项目

在很多较早期的 Java Web 系统中,前后端并未彻底分离,页面渲染仍由后端负责,接口只是页面交互的补充。这类系统中 @Controller 更常见。


四、@ResponseBody 到底做了什么

要讲清 @RestController,必须先讲 @ResponseBody

1. 核心作用

@ResponseBody 的作用不是“返回 JSON”,而是:

告诉 Spring:方法返回值不要再交给视图解析器,而是直接写入 HTTP 响应体。

至于最终写成什么格式,由返回值类型、请求头、响应头、消息转换器等共同决定。Spring 官方文档说明,带有 @ResponseBody 的返回值会通过 HttpMessageConverter 转换并写入响应。citeturn0search6turn0search1

2. 常见转换结果

返回普通对象

@ResponseBody
@GetMapping("/info")
public UserVO info() {
    return new UserVO(1L, "Tom");
}

若项目中存在 Jackson 相关依赖,通常会序列化为 JSON。

返回字符串

@ResponseBody
@GetMapping("/text")
public String text() {
    return "ok";
}

此时响应体内容通常就是 ok,而不是视图名 ok

返回 ResponseEntity

@ResponseBody
@GetMapping("/result")
public ResponseEntity<UserVO> result() {
    return ResponseEntity.ok(new UserVO(1L, "Tom"));
}

Spring 官方文档将 ResponseEntity 归类为可直接指定完整 HTTP 响应的返回类型。citeturn0search6

3. 类级别生效

@ResponseBody 可以放在类上:

@Controller
@ResponseBody
@RequestMapping("/api")
public class UserController {
}

一旦放在类上,相当于这个类中所有请求处理方法都默认走响应体写出模式。Spring 官方文档明确说明了这一点,并指出这正是 @RestController 的效果。citeturn0search1


五、@RestController 的本质与语义

1. 定义

@RestController 是 Spring 4.0 引入的组合注解,它本身同时标注了 @Controller@ResponseBody。这是 Spring 官方 API 的直接定义。citeturn0search0

也就是说,下面两段代码从语义上是等价的:

@RestController
@RequestMapping("/user")
public class UserController {
}

等价于:

@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
}

2. 它解决了什么问题

它主要解决的是接口开发中的重复标注问题。

在 REST 风格接口中,几乎所有方法都要返回 JSON、文本、文件或标准化响应对象。如果还使用 @Controller,就必须在每个方法上都写 @ResponseBody,非常冗余。

例如:

@Controller
@RequestMapping("/api/user")
public class UserController {

    @GetMapping("/{id}")
    @ResponseBody
    public UserVO getById(@PathVariable Long id) {
        return new UserVO(id, "Tom");
    }

    @PostMapping
    @ResponseBody
    public UserVO create(@RequestBody UserCreateDTO dto) {
        return new UserVO(1L, dto.getName());
    }
}

改成 @RestController 后会简洁很多:

@RestController
@RequestMapping("/api/user")
public class UserController {

    @GetMapping("/{id}")
    public UserVO getById(@PathVariable Long id) {
        return new UserVO(id, "Tom");
    }

    @PostMapping
    public UserVO create(@RequestBody UserCreateDTO dto) {
        return new UserVO(1L, dto.getName());
    }
}

3. 典型适用场景

3.1 前后端分离项目

当前主流开发模式下,前端通常由 Vue、React、Angular、小程序、App 等独立实现,后端只提供 REST API,这种场景优先使用 @RestController

3.2 微服务接口

服务之间通过 HTTP/JSON 通信时,控制器职责通常就是对外暴露数据接口,不需要视图层,@RestController 更符合语义。

3.3 纯接口后端

例如:

  • 管理后台 API
  • 公众号接口
  • 网关转发接口
  • BFF 层
  • 开放平台接口
  • 第三方对接回调处理

这些场景下几乎不会进行页面渲染,使用 @RestController 更清晰。


六、两者最核心的区别到底是什么

很多文章喜欢做对比表,但如果只写“一个返回页面,一个返回 JSON”,结论会过于粗糙。真正严谨的区别,可以从以下几个维度理解。

1. 语义层面的区别

  • @Controller:定义一个 MVC 控制器,默认返回值可参与视图解析
  • @RestController:定义一个默认把返回值写入响应体的控制器

2. 返回值解释方式不同

同样是返回 "success"

@Controller

@Controller
public class DemoController {

    @GetMapping("/demo")
    public String demo() {
        return "success";
    }
}

这里的 "success" 默认会被当作视图名。

@RestController

@RestController
public class DemoController {

    @GetMapping("/demo")
    public String demo() {
        return "success";
    }
}

这里的 "success" 会直接写入 HTTP 响应体。

这正是最容易埋坑的地方。很多开发者把原本用于页面跳转的方法迁移到 @RestController 类中后,发现页面不再渲染,而是浏览器直接显示一段字符串,本质原因就在这里。

3. 是否需要逐个方法声明 @ResponseBody

  • @Controller:如果要返回响应体,通常需要在方法上额外写 @ResponseBody
  • @RestController:默认全部方法都具备 @ResponseBody 语义

4. 适用架构不同

  • @Controller:更适合 MVC 页面应用
  • @RestController:更适合 REST API 与前后端分离架构

七、通过完整示例彻底看懂差异

1. 页面控制器示例

@Controller
@RequestMapping("/page/user")
public class UserPageController {

    @GetMapping("/list")
    public String list(Model model) {
        model.addAttribute("title", "用户列表");
        model.addAttribute("users", List.of("Tom", "Jack", "Lucy"));
        return "user/list";
    }
}

假设项目接入了 Thymeleaf,并存在 templates/user/list.html 模板,那么访问 /page/user/list 时,Spring 会进行视图解析与 HTML 渲染。

2. 接口控制器示例

@RestController
@RequestMapping("/api/user")
public class UserRestController {

    @GetMapping("/list")
    public List<UserVO> list() {
        return List.of(
                new UserVO(1L, "Tom"),
                new UserVO(2L, "Jack"),
                new UserVO(3L, "Lucy")
        );
    }
}

这里返回的是对象集合,通常会被转换为 JSON 数组。

3. 混合写法示例

@Controller
@RequestMapping("/user")
public class MixedUserController {

    @GetMapping("/page")
    public String page(Model model) {
        model.addAttribute("title", "用户页面");
        return "user/page";
    }

    @GetMapping("/api/{id}")
    @ResponseBody
    public UserVO api(@PathVariable Long id) {
        return new UserVO(id, "Tom");
    }
}

这个类中同一时间既有页面渲染方法,也有 JSON 接口方法。

4. 容易误判的字符串返回案例

@RestController
@RequestMapping("/hello")
public class HelloController {

    @GetMapping
    public String hello() {
        return "index";
    }
}

不少开发者会误以为这里会渲染 index.html。事实上不会,它会直接输出字符串 index。因为 @RestController 已经让整个类拥有了 @ResponseBody 语义。这个结论与 Spring 官方定义完全一致。citeturn0search0turn0search1


八、底层执行机制:为什么 @RestController 会直接输出 JSON

1. 消息转换器参与响应输出

当请求处理方法带有 @ResponseBody 语义时,Spring 会把方法返回值交给 HttpMessageConverter

常见转换器包括:

  • MappingJackson2HttpMessageConverter:对象转 JSON
  • StringHttpMessageConverter:字符串输出
  • ByteArrayHttpMessageConverter:字节数组输出
  • 其他 XML、表单、资源类型相关转换器

在 Spring Boot Web 场景下,只要类路径中存在 Jackson,返回普通 Java 对象时通常会自动转为 JSON。Spring 官方入门示例也明确说明了这一点。citeturn0search9

2. 为什么 @Controller 默认不这样处理

因为 @Controller 的默认设计目标是 MVC 页面控制器。对于这种控制器,Spring 会优先按照视图模型模式解释返回值,例如:

  • String 作为视图名
  • ModelAndView 作为视图与模型
  • ModelMap 用于补充模型数据

Spring 官方文档在控制器方法返回值说明中,也把 String 视图名、ViewModelAndViewModel 等都作为标准返回形式列出。citeturn0search6

3. 返回值不是“JSON”,而是“响应体”

这一点非常重要。

很多开发者把 @RestController 理解成“JSON 控制器”,其实不准确。它控制的不是“必须返回 JSON”,而是“返回值写入响应体”。

所以这些都是合法且常见的:

  • 返回 JSON 对象
  • 返回纯文本
  • 返回文件流
  • 返回字节数组
  • 返回 ResponseEntity<byte[]>
  • 返回 StreamingResponseBody
  • 返回 SseEmitter

Spring 官方返回值说明中也把这些类型都纳入可直接写出响应的支持范围。citeturn0search6


九、开发中最常见的误区

1. 误区一:@Controller 不能返回 JSON

错误。

@Controller 完全可以返回 JSON,只要在方法上加 @ResponseBody,或者在类上加 @ResponseBody@RestController 只是对这种写法的封装。citeturn0search0turn0search1

2. 误区二:@RestController 只能用于 RESTful 风格接口

不严谨。

它更适合 REST API,但并不要求接口必须完全符合 RESTful 资源设计规范。只要你的控制器方法主要是把结果写到响应体里,使用 @RestController 就是合理的。

3. 误区三:@RestController 一定返回 JSON

错误。

它返回的是响应体,JSON 只是最常见的一种表现形式。最终格式由消息转换器与协商结果决定。

4. 误区四:返回 String 时两者没有区别

错误,而且是线上问题高发点。

  • @Controller 返回 String,默认通常是视图名
  • @RestController 返回 String,默认通常是响应文本

5. 误区五:一个项目里只能二选一

错误。

同一个项目完全可以同时存在:

  • 页面控制器使用 @Controller
  • API 控制器使用 @RestController

这其实是最合理的分层方式。


十、如何在真实项目中做选型

1. 纯前后端分离项目

直接优先使用 @RestController

这类项目中后端职责通常只有:

  • 参数接收
  • 参数校验
  • 调用服务
  • 返回统一响应体
  • 异常统一处理

页面渲染不由后端负责,因此使用 @Controller 没有必要,除非某些特殊端点需要跳转页面。

2. 服务端渲染项目

优先使用 @Controller

如果项目核心是:

  • 后台管理模板
  • CMS 模板页面
  • Thymeleaf 页面
  • JSP 页面

那么控制器的主要职责是视图返回,此时 @Controller 更准确。

3. 混合型项目

最推荐的做法不是在同一个类里混合使用,而是按职责拆分控制器

  • xxxPageController:使用 @Controller
  • xxxApiController:使用 @RestController

这样做的好处是:

  • 语义清晰
  • 维护成本低
  • 避免字符串返回值语义混乱
  • 统一异常、统一响应封装更容易实施

4. 存量项目改造

如果老项目原本是传统 MVC,正在逐步前后端分离,建议不要粗暴地把所有 @Controller 直接替换成 @RestController。应当先识别每个控制器的职责:

  • 返回模板页面的,继续保留 @Controller
  • 提供数据接口的,改造为 @RestController
  • 页面与接口混合严重的,优先拆分控制器类

十一、统一返回结构时该如何选择

在企业项目中,接口通常会采用统一响应格式,例如:

public class ApiResponse<T> {
    private int code;
    private String message;
    private T data;
}

对应控制器:

@RestController
@RequestMapping("/api/order")
public class OrderController {

    @GetMapping("/{id}")
    public ApiResponse<OrderVO> getById(@PathVariable Long id) {
        OrderVO vo = new OrderVO(id, "PAID");
        return new ApiResponse<>(200, "success", vo);
    }
}

这种写法与 @RestController 非常契合,因为控制器的职责就是输出标准化响应体。

如果用 @Controller 也能实现,但通常会显得多余:

@Controller
@RequestMapping("/api/order")
public class OrderController {

    @GetMapping("/{id}")
    @ResponseBody
    public ApiResponse<OrderVO> getById(@PathVariable Long id) {
        OrderVO vo = new OrderVO(id, "PAID");
        return new ApiResponse<>(200, "success", vo);
    }
}

语义上没错,但风格上不如 @RestController 直接。


十二、异常处理与这两个注解的关系

异常处理本身并不依赖 @Controller@RestController 才能生效,但返回结果的表达方式会受到 @ResponseBody 语义影响。

1. 页面项目中的异常处理

若希望异常后跳转错误页,往往会结合 @ControllerAdvice 与视图返回:

@ControllerAdvice
public class GlobalPageExceptionHandler {

    @ExceptionHandler(Exception.class)
    public String handle(Exception e, Model model) {
        model.addAttribute("msg", e.getMessage());
        return "error/500";
    }
}

2. 接口项目中的异常处理

若希望异常后直接返回 JSON,则常用 @RestControllerAdvice。Spring 官方文档明确指出,@RestControllerAdvice@ControllerAdvice + @ResponseBody 的快捷组合。citeturn0search10

@RestControllerAdvice
public class GlobalApiExceptionHandler {

    @ExceptionHandler(IllegalArgumentException.class)
    public ApiResponse<Void> handle(IllegalArgumentException e) {
        return new ApiResponse<>(400, e.getMessage(), null);
    }
}

因此,在接口型系统中,@RestController 往往会与 @RestControllerAdvice 配套使用,形成统一的响应风格。


十三、关于文件下载、文本输出、二进制响应的区别理解

很多人把 @RestController 理解得太窄,只盯着 JSON。实际上,在下面这些场景中,@RestController 同样很常见。

1. 下载文件

@RestController
@RequestMapping("/file")
public class FileController {

    @GetMapping("/download")
    public ResponseEntity<byte[]> download() {
        byte[] content = "hello".getBytes(StandardCharsets.UTF_8);
        return ResponseEntity.ok()
                .header("Content-Disposition", "attachment; filename=test.txt")
                .body(content);
    }
}

这里返回的不是 JSON,而是字节数组响应。

2. 输出纯文本

@RestController
@RequestMapping("/ping")
public class PingController {

    @GetMapping
    public String ping() {
        return "pong";
    }
}

3. 流式输出

Spring 官方返回值列表中也包含 StreamingResponseBodyResponseBodyEmitterSseEmitter 等类型,说明 @ResponseBody 语义并不局限于普通 JSON 接口。citeturn0search6

所以,不要把 @RestController 简化成“JSON 控制器”,它本质上是“响应体控制器”。


十四、版本维度上需要注意什么

1. @RestController 的引入版本

@RestController 自 Spring Framework 4.0 起提供,这一点从官方 API 文档可以确认。citeturn0search0turn0search7

因此:

  • Spring 4.x、5.x、6.x、7.x 中都可以正常使用
  • 如果维护的是更早的 Spring 3.x 项目,则通常只能使用 @Controller + @ResponseBody

2. Spring Boot 常见项目中的实际情况

现代 Spring Boot 项目基本都构建在较新的 Spring Framework 版本之上,因此在绝大多数实际开发中,@RestController 都是默认可用的。

3. 版本差异的重点不在注解本身,而在生态默认行为

例如:

  • 默认 JSON 序列化组件
  • 错误响应结构
  • 内容协商策略
  • Jakarta EE 命名空间变化
  • Web MVC 与 WebFlux 的支持差异

但就 @Controller@RestController 的核心语义而言,这些年变化并不大,核心理解仍然是:

  • 一个是 MVC 控制器
  • 一个是默认带 @ResponseBody 语义的控制器

十五、企业项目中的最佳实践建议

1. 页面控制器与接口控制器分离

推荐明确命名:

  • UserPageController
  • UserApiController

不要把页面与接口方法胡乱堆在一个类中,否则很容易因为字符串返回值语义不同而产生隐蔽问题。

2. 前后端分离项目统一使用 @RestController

如果整个服务只暴露接口,没有模板页面,就统一采用 @RestController,这样团队风格一致,代码更简洁。

3. 需要返回页面时只用 @Controller

不要为了“统一风格”强行在页面项目中全部改用 @RestController,这会让视图返回逻辑变得别扭,甚至直接失效。

4. 接口统一返回 ResponseEntity 或统一响应对象

对于需要严格控制状态码、响应头、缓存策略、下载行为的接口,优先使用 ResponseEntity。Spring 官方也把它作为完整 HTTP 响应的标准返回方式。citeturn0search6

5. 配套统一异常处理

  • 页面项目:@ControllerAdvice
  • 接口项目:@RestControllerAdvice

6. 不要滥用字符串返回值

@RestController 中返回 String 虽然合法,但如果项目统一返回 JSON,对外接口最好直接返回对象或统一包装对象,避免接口风格不一致。


十六、一张表彻底总结区别

对比维度 @Controller @RestController
本质 MVC 控制器注解 组合注解,等价于 @Controller + @ResponseBody
默认返回值语义 优先用于视图解析 默认写入 HTTP 响应体
是否需要 @ResponseBody 返回响应体时通常需要额外声明 默认已具备
典型返回内容 视图名、ModelAndView、页面模型 JSON、文本、文件、二进制、ResponseEntity
典型场景 Thymeleaf/JSP/FreeMarker 页面项目 前后端分离、REST API、微服务
返回 String 时含义 通常是视图名 通常是响应文本
是否能返回 JSON 能,配合 @ResponseBody 能,且默认支持响应体语义
推荐搭配 @ControllerAdvice @RestControllerAdvice

十七、结论

@RestController@Controller 的区别,表面上看是“返回 JSON 还是返回页面”,但本质上是Spring 如何解释控制器方法返回值的区别。

更准确地说:

  • @Controller 代表一个标准 MVC 控制器,默认更适合视图解析与页面渲染
  • @RestController@Controller@ResponseBody 的组合,表示该控制器中的返回值默认直接写入响应体,而不是参与视图解析。这个语义是 Spring 官方明确定义的。citeturn0search0turn0search1

在实际项目中,应该按照控制器职责做选择:

  • 做页面渲染,用 @Controller
  • 做接口输出,用 @RestController
  • 混合项目中按职责拆分,而不是为了省事全部混用

只有这样理解,才能真正避开字符串返回值误判、视图失效、接口风格不统一等常见问题,也才能在 Spring MVC、Spring Boot、前后端分离与微服务实践中做出正确选型。

正文到此结束
评论插件初始化中...
Loading...