@ResponseBody 详解:Spring MVC 中把返回值直接写入响应体
一、它是什么
@ResponseBody 是 Spring MVC 提供的注解,作用是告诉 Spring MVC:这个方法的返回值不要交给视图解析器处理,直接写入 HTTP 响应体。
不加 @ResponseBody 时,Controller 方法返回 String,Spring MVC 会把它当作视图名 ,交给 ViewResolver 去找模板:
java
@Controller
public class PageController {
@GetMapping("/hello")
public String hello() {
return "hello"; // 找 hello.html 模板
}
}
加了 @ResponseBody 后,同一个返回值变成响应体内容:
java
@Controller
public class TextController {
@GetMapping("/hello")
@ResponseBody
public String hello() {
return "hello"; // 响应体就是 hello
}
}
一句话概括:@ResponseBody 把"返回视图名"切换成"返回响应体数据"。
二、它解决什么问题
传统 Spring MVC 中,Controller 的职责是"准备数据 + 选择视图",最终由 JSP、Thymeleaf 渲染页面。
但在前后端分离或 RESTful 接口场景下,后端只需要返回数据,不需要渲染页面。@ResponseBody 就是为这种场景准备的:
- 返回 JSON 对象,交给 Jackson 序列化。
- 返回字符串,直接写文本。
- 返回字节数组,直接写二进制。
- 返回
ResponseEntity,携带状态码和响应头。
它把 Controller 从"视图渲染"中解放出来,让方法返回值直接成为 HTTP 响应内容。
三、底层工作原理
1. 处理链路
请求到达 DispatcherServlet
→ 匹配 HandlerMethod
→ RequestMappingHandlerAdapter 调用方法
→ 方法返回对象
→ RequestResponseBodyMethodProcessor 处理返回值
→ 遍历 HttpMessageConverter
→ 找到能处理返回值类型的转换器
→ 调用 write() 写入响应体
→ 不再调用 ViewResolver
关键类是 RequestResponseBodyMethodProcessor,它同时负责读取 @RequestBody 参数和写出 @ResponseBody 返回值。
2. 判断条件
RequestResponseBodyMethodProcessor.supportsReturnType() 的判断逻辑:
java
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}
只要方法或类 上有 @ResponseBody,就由它处理返回值。
3. 选择转换器
返回对象后,AbstractMessageConverterMethodProcessor.writeWithMessageConverters() 会:
- 获取返回值的实际类型。
- 获取请求的
Accept头。 - 遍历所有
HttpMessageConverter。 - 调用
canWrite(返回类型, 媒体类型)找到匹配的转换器。 - 调用
write()把对象写入响应体,并设置Content-Type。
常见转换器对应关系:
| 返回类型 | 转换器 | Content-Type |
|---|---|---|
String |
StringHttpMessageConverter |
text/plain |
| 普通对象 | MappingJackson2HttpMessageConverter |
application/json |
byte[] |
ByteArrayHttpMessageConverter |
application/octet-stream |
Resource |
ResourceHttpMessageConverter |
文件对应的类型 |
4. 用图串起来
Controller 返回对象
│
▼
RequestResponseBodyMethodProcessor
│
├── 没有 @ResponseBody → 交给 ViewResolver 处理
│
└── 有 @ResponseBody
│
▼
遍历 HttpMessageConverter
│
▼
找到匹配的转换器,调用 write()
│
▼
写入响应体,设置 Content-Type
四、@ResponseBody 和 @RestController
@RestController 是 @Controller 和 @ResponseBody 的组合注解:
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
}
因此:
java
@RestController
public class UserController {
}
等价于:
java
@Controller
@ResponseBody
public class UserController {
}
类上加了 @RestController,类中所有方法都隐含 @ResponseBody,不需要逐个方法标注。
| 对比 | @Controller |
@RestController |
|---|---|---|
| 返回值处理 | 默认返回视图名 | 直接写响应体 |
是否隐含 @ResponseBody |
否 | 是 |
| 典型用途 | 页面渲染 | RESTful JSON 接口 |
五、代码示例
1. 返回 JSON 对象
java
@RestController
@RequestMapping("/api/user")
public class UserController {
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}
}
返回 User 对象,Jackson 转换器把它序列化成 JSON:
json
{"id":1,"username":"jack","email":"jack@example.com"}
2. 返回字符串
java
@GetMapping("/text")
public String text() {
return "hello";
}
StringHttpMessageConverter 优先级高于 Jackson,响应体是纯文本:
hello
不是 "hello"。如果希望返回 JSON 字符串,需要声明 produces = "application/json":
java
@GetMapping(value = "/json-string", produces = "application/json")
public String jsonString() {
return "hello";
}
输出:
json
"hello"
3. 配合 ResponseEntity
java
@GetMapping("/user/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userService.findById(id);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(user);
}
ResponseEntity 携带状态码、响应头和响应体。响应体部分仍然由 HttpMessageConverter 写出。
4. 混合使用 @Controller 和 @ResponseBody
java
@Controller
public class MixedController {
@GetMapping("/page")
public String page() {
return "index"; // 返回视图名
}
@GetMapping("/data")
@ResponseBody
public User data() {
return new User(1L, "jack"); // 返回 JSON
}
}
同一个类中,没有 @ResponseBody 的方法走视图解析,有 @ResponseBody 的方法直接写响应体。
六、和 @RequestBody 的区别
| 对比 | @ResponseBody |
@RequestBody |
|---|---|---|
| 方向 | 出:Java 对象 → 响应体 | 入:请求体 → Java 对象 |
| 位置 | 方法或类上 | 方法参数上 |
| 依赖组件 | HttpMessageConverter.write() |
HttpMessageConverter.read() |
| 典型场景 | 返回 JSON | 接收 JSON |
一句话记忆:@RequestBody 读进来,@ResponseBody 写出去。
七、常见问题
1. 返回对象变成 404 或模板找不到
原因:使用了 @Controller 但没有加 @ResponseBody,Spring MVC 把返回值当视图名去找模板。
java
@Controller
public class UserController {
@GetMapping("/user")
public User getUser() {
return new User(); // 会被当作视图名,报 TemplateInputException
}
}
解决:加 @ResponseBody,或改用 @RestController。
2. 返回 String 没有双引号
这是正常行为。StringHttpMessageConverter 优先级高于 Jackson,字符串被当作纯文本。需要 JSON 字符串时声明 produces = "application/json"。
3. 406 Not Acceptable
客户端 Accept 头不接受任何转换器支持的媒体类型。比如接口只支持 JSON,客户端却发 Accept: text/html。
解决:检查客户端 Accept 头,或后端用 produces 声明响应类型。
4. 415 和 @ResponseBody 无关
415 是读请求体失败,通常和 @RequestBody 或 Content-Type 有关。@ResponseBody 只负责写响应体。
5. 中文乱码
返回 String 时,StringHttpMessageConverter 默认字符集是 ISO_8859_1,中文可能乱码。解决:
java
@GetMapping(value = "/text", produces = "text/plain;charset=UTF-8")
或者配置 StringHttpMessageConverter 的默认字符集为 UTF-8。
6. 返回值是 void
方法返回 void 且没有手动写响应体时,@ResponseBody 不会触发任何转换。需要手动通过 HttpServletResponse 写出。
八、记忆方法
1. 一句话定位
有 ResponseBody,返回值直接进响应体;
没有 ResponseBody,返回值当视图名。
2. 两个组合
@Controller + @ResponseBody = @RestController
@ResponseBody 写出,@RequestBody 读入
3. 处理链路口诀
返回值 → RequestResponseBodyMethodProcessor → 遍历转换器 → write → 响应体
4. 转换器优先级口诀
String 在前,Jackson 在后,
返回字符串纯文本,
返回对象才走 JSON。
5. 常见错误口诀
对象变 404,多半忘了 ResponseBody;
String 没引号,StringHttpMessageConverter 正常行为;
406 是 Accept 不匹配。
九、总结
| 维度 | 说明 |
|---|---|
| 作用 | 方法返回值直接写入 HTTP 响应体,不经过视图解析器 |
| 位置 | 方法或类上 |
| 核心处理器 | RequestResponseBodyMethodProcessor |
| 依赖组件 | HttpMessageConverter |
| 组合注解 | @RestController = @Controller + @ResponseBody |
与 @RequestBody 区别 |
一个写出,一个读入 |
| 常见问题 | 忘加注解导致 404、返回 String 无引号、406、中文乱码 |
@ResponseBody 是 Spring MVC 中"返回值不渲染页面、直接输出数据"的开关。理解它,就能解释为什么 @RestController 的方法能直接返回 JSON、为什么 @Controller 方法返回对象会去找模板、为什么返回 String 没有双引号。记住"有它写响应体、没它找视图名",再记住"String 优先、对象走 Jackson",这类返回值处理问题就能快速判断。