@ResponseBody 详解:Spring MVC 中把返回值直接写入响应体

@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() 会:

  1. 获取返回值的实际类型。
  2. 获取请求的 Accept 头。
  3. 遍历所有 HttpMessageConverter。
  4. 调用 canWrite(返回类型, 媒体类型) 找到匹配的转换器。
  5. 调用 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",这类返回值处理问题就能快速判断。

相关推荐
jason成都3 小时前
RTKLIB 的 Java 移植版来了:rtklib-java v3.3.0 功能全景
java·gnss·rtklib·rtklib_java
LazyHunter93 小时前
JDK 28:Java 下一步方向解析与新特性展望
java·jdk
m0_639906943 小时前
Java 重写 Override 与重载 Overload 区别、访问修饰符、static 关键字
java·开发语言
GIS数据转换器3 小时前
基于CIM的城市智慧供热监管服务平台
java·大数据·开发语言·人工智能·后端·物联网·struts
m0_639906944 小时前
SpringBoot自动配置原理
java·spring boot·后端
海鸥-w4 小时前
数据库冗余字段设计笔记
java·数据库·笔记·oracle
工具派4 小时前
PDF签名贴上去为什么发虚?尺寸、DPI与坐标的边界实测
java·python·pdf
萧瑟余晖5 小时前
Spring Data综合实战与调优详解
java·后端·spring