ResponseEntity
本章在
@ResponseBody的基础上更进一步。@ResponseBody解决了"数据怎么出去"的问题,但它对 HTTP 响应的控制是有限的——状态码固定 200,响应头无法自定义。ResponseEntity提供了对 HTTP 响应的完整控制权:状态码、响应头、响应体,三者皆可定制。这是编写专业级 RESTful API 的必备技能。
定义与作用
ResponseEntity<T> 是 Spring MVC 提供的完整 HTTP 响应封装类,它同时携带:
- HTTP 状态码(Status Code):200、201、404、500 等
- 响应头(Headers):
Content-Type、Location、Cache-Control等 - 响应体(Body):任意类型的数据对象
使用 ResponseEntity,Controller 方法不再只是"返回数据",而是"返回一个完整的 HTTP 响应"。
生活类比:正式公函
想象飞翔科技对外发送商务公函:
- @ResponseBody:直接打电话告诉对方结果(只有内容,没有正式格式)
- ResponseEntity:发送一份正式公函,包含:
- 信封上的红色印章(HTTP 状态码:200 表示正常,404 表示查无此人)
- 信封上的附加说明(响应头:"请查收附件"、"此函限今日回复")
- 信纸正文(响应体:具体的业务数据)
专业的 RESTful API 就像正式公函——状态码传达结果类别,响应头传递元信息,响应体承载具体数据。
核心原理
ResponseEntity 构建链
构建方式:
Spring 提供了流畅的 Builder API 创建 ResponseEntity:
ResponseEntity.status(HttpStatus.CREATED) // 状态码
.header("Location", "/employees/101") // 自定义头
.body(employee); // 响应体
快捷静态方法:
ResponseEntity.ok(body); // 200 OK
ResponseEntity.notFound().build(); // 404 Not Found
ResponseEntity.noContent().build(); // 204 No Content
适用位置与常用方法
ResponseEntity 作为返回值类型,用于 Controller 方法的返回。
| 构建方法 | 说明 |
|---|---|
ResponseEntity.status(int) / .status(HttpStatus) | 设置状态码 |
.header(String, String...) | 添加响应头 |
.headers(HttpHeaders) | 批量设置响应头 |
.body(T) | 设置响应体 |
.build() | 无响应体时构建(用于 204、404 等) |
常见状态码使用场景表
| 状态码 | 含义 | 使用场景 | ResponseEntity 写法 |
|---|---|---|---|
| 200 | OK | 查询成功、更新成功 | ResponseEntity.ok(body) |
| 201 | Created | 资源创建成功 | ResponseEntity.status(201).body(body) |
| 204 | No Content | 删除成功、无返回数据 | ResponseEntity.noContent().build() |
| 400 | Bad Request | 参数校验失败 | ResponseEntity.badRequest().body(error) |
| 401 | Unauthorized | 未登录/令牌无效 | ResponseEntity.status(401).body(error) |
| 403 | Forbidden | 无权限 | ResponseEntity.status(403).body(error) |
| 404 | Not Found | 资源不存在 | ResponseEntity.notFound().build() |
| 409 | Conflict | 资源冲突(如重复创建) | ResponseEntity.status(409).body(error) |
| 500 | Internal Server Error | 服务器内部错误 | ResponseEntity.status(500).body(error) |
完整示例
场景
飞翔科技员工管理系统的 RESTful API 需要遵循 HTTP 语义规范。架构师白歌要求:创建员工返回 201 + Location 头,查询不到返回 404,删除成功返回 204。
代码实现
package com.feixiang.web;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.net.URI;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/api/employees")
public class EmployeeController {
private final List<Employee> employees = Arrays.asList(
new Employee(1L, "张三", "技术部", 25000),
new Employee(2L, "李四", "产品部", 22000)
);
/**
* 查询单个员工:存在返回 200,不存在返回 404
*/
@GetMapping("/{id}")
public ResponseEntity<Employee> getEmployee(@PathVariable Long id) {
Optional<Employee> emp = employees.stream()
.filter(e -> e.getId().equals(id))
.findFirst();
return emp.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
/**
* 创建员工:返回 201 + Location 头
*/
@PostMapping
public ResponseEntity<Employee> createEmployee(@RequestBody Employee employee) {
employee.setId(100L);
// 实际项目中保存到数据库
URI location = URI.create("/api/employees/" + employee.getId());
return ResponseEntity.created(location)
.body(employee);
}
/**
* 删除员工:返回 204 No Content
*/
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteEmployee(@PathVariable Long id) {
// 实际项目中从数据库删除
return ResponseEntity.noContent().build();
}
/**
* 参数错误:返回 400 + 错误信息
*/
@GetMapping("/search")
public ResponseEntity<?> search(@RequestParam(required = false) String name) {
if (name == null || name.trim().isEmpty()) {
return ResponseEntity.badRequest()
.body("{\"error\": \"name 参数不能为空\"}");
}
return ResponseEntity.ok(employees);
}
}
// Employee.java
package com.feixiang.web;
public class Employee {
private Long id;
private String name;
private String department;
private Integer salary;
public Employee() {}
public Employee(Long id, String name, String department, Integer salary) {
this.id = id;
this.name = name;
this.department = department;
this.salary = salary;
}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDepartment() { return department; }
public void setDepartment(String department) { this.department = department; }
public Integer getSalary() { return salary; }
public void setSalary(Integer salary) { this.salary = salary; }
}
HTTP 请求示例
示例 1:查询存在员工(200 OK)
curl -i "http://localhost:8080/api/employees/1"
响应:
HTTP/1.1 200
Content-Type: application/json
{"id":1,"name":"张三","department":"技术部","salary":25000}
示例 2:查询不存在员工(404 Not Found)
curl -i "http://localhost:8080/api/employees/999"
响应:
HTTP/1.1 404
Content-Length: 0
示例 3:创建员工(201 Created + Location)
curl -i -X POST "http://localhost:8080/api/employees" \
-H "Content-Type: application/json" \
-d '{"name":"王五","department":"运维部","salary":20000}'
响应:
HTTP/1.1 201
Location: /api/employees/100
Content-Type: application/json
{"id":100,"name":"王五","department":"运维部","salary":20000}
示例 4:删除员工(204 No Content)
curl -i -X DELETE "http://localhost:8080/api/employees/1"
响应:
HTTP/1.1 204
Content-Length: 0
易错场景与面试考点
误区一:ResponseEntity 和 @ResponseBody 同时用
现象:方法返回 ResponseEntity,同时标注 @ResponseBody。
纠正:ResponseEntity 已经隐含了 @ResponseBody 的语义——它的 body 会被 HttpMessageConverter 序列化写入响应体。额外加 @ResponseBody 是冗余的,但不会报错。
// 正确:不需要 @ResponseBody
@GetMapping("/{id}")
public ResponseEntity<Employee> getEmployee(@PathVariable Long id) {
return ResponseEntity.ok(employee);
}
误区二:泛型类型擦除导致序列化问题
现象:ResponseEntity<?> 返回时,Jackson 无法确定具体类型,某些字段可能丢失。
纠正:尽量使用明确的泛型类型:
// 推荐
public ResponseEntity<Employee> getEmployee(...) { ... }
// 避免在确定类型时使用通配符
public ResponseEntity<?> getEmployee(...) { ... } // 仅在类型不确定时使用
误区三:201 Created 忘记加 Location 头
错误:创建资源返回 201,但响应头中没有 Location。
纠正:HTTP/1.1 规范建议 201 响应包含指向新创建资源的 Location 头。这是 RESTful API 的最佳实践:
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(employee.getId())
.toUri();
return ResponseEntity.created(location).body(employee);
面试高频:ResponseEntity 的优势是什么?
标准回答:
- 完整控制 HTTP 响应:可以设置任意状态码(200、201、404 等),而
@ResponseBody固定返回 200 - 自定义响应头:如
Location、Cache-Control、X-Request-ID等 - RESTful 语义化:不同业务场景返回不同状态码,让 API 更符合 HTTP 规范
- 类型安全:泛型参数
ResponseEntity<T>保证响应体类型明确 - 链式构建:
ResponseEntity.status().header().body()流畅 API,代码简洁
小结
ResponseEntity<T> 是 Spring MVC 中控制 HTTP 响应的终极武器。它将状态码、响应头、响应体封装为一个对象,让开发者可以精确控制每一个 HTTP 响应细节。
核心要点:
ResponseEntity同时控制状态码、响应头、响应体- 常用快捷方法:
ok()、created()、noContent()、notFound()、badRequest() - 创建资源返回 201 +
Location头是 RESTful 最佳实践 - 删除成功返回 204 No Content,查询不到返回 404
本章与全局的关系:本章讲解了"如何完整控制 HTTP 响应"。下一节 ViewResolver 将回到传统视图渲染模式,讲解 Spring MVC 如何将逻辑视图名解析为物理页面路径。