Spring 中的 @RestController 和 @Controller 详解与区别
- 发布时间:2026-03-19 23:36:42
- 本文热度:浏览 10 赞 0 评论 0
- 文章标签: Spring MVC Spring Boot Java
- 全文共1字,阅读约需1分钟
一、为什么这个问题经常被讲错
在 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 需要决定两件事:
- 这个返回值是不是“模型 + 视图”的一部分
- 这个返回值是不是应该经过
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 的效果。citeturn0search1turn0search0
所以,控制器注解的选择,实际上是在定义控制器的默认返回值语义。
三、@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 转换并写入响应。citeturn0search6turn0search1
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 响应的返回类型。citeturn0search6
3. 类级别生效
@ResponseBody 可以放在类上:
@Controller
@ResponseBody
@RequestMapping("/api")
public class UserController {
}
一旦放在类上,相当于这个类中所有请求处理方法都默认走响应体写出模式。Spring 官方文档明确说明了这一点,并指出这正是 @RestController 的效果。citeturn0search1
五、@RestController 的本质与语义
1. 定义
@RestController 是 Spring 4.0 引入的组合注解,它本身同时标注了 @Controller 和 @ResponseBody。这是 Spring 官方 API 的直接定义。citeturn0search0
也就是说,下面两段代码从语义上是等价的:
@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 官方定义完全一致。citeturn0search0turn0search1
八、底层执行机制:为什么 @RestController 会直接输出 JSON
1. 消息转换器参与响应输出
当请求处理方法带有 @ResponseBody 语义时,Spring 会把方法返回值交给 HttpMessageConverter。
常见转换器包括:
MappingJackson2HttpMessageConverter:对象转 JSONStringHttpMessageConverter:字符串输出ByteArrayHttpMessageConverter:字节数组输出- 其他 XML、表单、资源类型相关转换器
在 Spring Boot Web 场景下,只要类路径中存在 Jackson,返回普通 Java 对象时通常会自动转为 JSON。Spring 官方入门示例也明确说明了这一点。citeturn0search9
2. 为什么 @Controller 默认不这样处理
因为 @Controller 的默认设计目标是 MVC 页面控制器。对于这种控制器,Spring 会优先按照视图模型模式解释返回值,例如:
String作为视图名ModelAndView作为视图与模型Model、Map用于补充模型数据
Spring 官方文档在控制器方法返回值说明中,也把 String 视图名、View、ModelAndView、Model 等都作为标准返回形式列出。citeturn0search6
3. 返回值不是“JSON”,而是“响应体”
这一点非常重要。
很多开发者把 @RestController 理解成“JSON 控制器”,其实不准确。它控制的不是“必须返回 JSON”,而是“返回值写入响应体”。
所以这些都是合法且常见的:
- 返回 JSON 对象
- 返回纯文本
- 返回文件流
- 返回字节数组
- 返回
ResponseEntity<byte[]> - 返回
StreamingResponseBody - 返回
SseEmitter
Spring 官方返回值说明中也把这些类型都纳入可直接写出响应的支持范围。citeturn0search6
九、开发中最常见的误区
1. 误区一:@Controller 不能返回 JSON
错误。
@Controller 完全可以返回 JSON,只要在方法上加 @ResponseBody,或者在类上加 @ResponseBody。@RestController 只是对这种写法的封装。citeturn0search0turn0search1
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:使用@ControllerxxxApiController:使用@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 的快捷组合。citeturn0search10
@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 官方返回值列表中也包含 StreamingResponseBody、ResponseBodyEmitter、SseEmitter 等类型,说明 @ResponseBody 语义并不局限于普通 JSON 接口。citeturn0search6
所以,不要把 @RestController 简化成“JSON 控制器”,它本质上是“响应体控制器”。
十四、版本维度上需要注意什么
1. @RestController 的引入版本
@RestController 自 Spring Framework 4.0 起提供,这一点从官方 API 文档可以确认。citeturn0search0turn0search7
因此:
- 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. 页面控制器与接口控制器分离
推荐明确命名:
UserPageControllerUserApiController
不要把页面与接口方法胡乱堆在一个类中,否则很容易因为字符串返回值语义不同而产生隐蔽问题。
2. 前后端分离项目统一使用 @RestController
如果整个服务只暴露接口,没有模板页面,就统一采用 @RestController,这样团队风格一致,代码更简洁。
3. 需要返回页面时只用 @Controller
不要为了“统一风格”强行在页面项目中全部改用 @RestController,这会让视图返回逻辑变得别扭,甚至直接失效。
4. 接口统一返回 ResponseEntity 或统一响应对象
对于需要严格控制状态码、响应头、缓存策略、下载行为的接口,优先使用 ResponseEntity。Spring 官方也把它作为完整 HTTP 响应的标准返回方式。citeturn0search6
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 官方明确定义的。citeturn0search0turn0search1
在实际项目中,应该按照控制器职责做选择:
- 做页面渲染,用
@Controller - 做接口输出,用
@RestController - 混合项目中按职责拆分,而不是为了省事全部混用
只有这样理解,才能真正避开字符串返回值误判、视图失效、接口风格不统一等常见问题,也才能在 Spring MVC、Spring Boot、前后端分离与微服务实践中做出正确选型。