统一响应与 ResponseEntity 使用
裸返回对象时,成功失败各自为政会让前端很难解析。成熟的接口都会约定「统一响应体」,而 ResponseEntity 是 Spring MVC 里同时控制状态码、响应头与响应体的官方武器。
先看问题
不统一时的返回:成功返回 User 对象,失败却返回一段 HTML 错误页或裸字符串,前端无法用一套逻辑处理。于是引入统一的包装类:
public class ApiResponse<T> {
private int code; // 0 成功,非 0 失败
private String message;
private T data;
public static <T> ApiResponse<T> ok(T data) {
return new ApiResponse<>(0, "success", data);
}
public static <T> ApiResponse<T> fail(int code, String message) {
return new ApiResponse<>(code, message, null);
}
// 构造方法与 getter 省略
}
ResponseEntity:状态 + 头 + 体一把抓
ResponseEntity<T> 继承 HttpEntity<T>(HttpEntity 只有头和体),额外携带 HttpStatus:
@RestController
public class UserApi {
@GetMapping("/api/users/{id}")
public ResponseEntity<ApiResponse<User>> getUser(@PathVariable Long id) {
User user = userService.findById(id);
if (user == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.fail(404, "用户不存在"));
}
return ResponseEntity.ok(ApiResponse.ok(user));
}
}
常用静态工厂
| 写法 | 等价语义 | 说明 |
|---|---|---|
| ResponseEntity.ok(body) | 200 + body | 最常用 |
| ResponseEntity.status(201).body(x) | 自定义状态码 | 灵活兜底 |
| ResponseEntity.noContent().build() | 204 | 不能带 body |
| ResponseEntity.badRequest().body(x) | 400 | 参数错误 |
| ResponseEntity.created(uri).body(x) | 201 + Location | 创建资源后用 |
其中 created 需要传资源的 URI,Spring 6 提供 UriComponentsBuilder 拼 URL:
@PostMapping("/api/users")
public ResponseEntity<ApiResponse<User>> create(@RequestBody UserDTO dto) {
User saved = userService.create(dto);
URI location = UriComponentsBuilder.fromPath("/api/users/{id}")
.buildAndExpand(saved.getId()).toUri();
return ResponseEntity.created(location).body(ApiResponse.ok(saved));
}
设置响应头
直接链式追加:
return ResponseEntity.ok()
.header("X-Trace-Id", traceId)
.contentType(MediaType.APPLICATION_JSON)
.body(ApiResponse.ok(data));
让异常也走统一格式
只包成功还不够,异常路径也要同样形状。标准做法是把统一响应与 @RestControllerAdvice 结合(第 29 章详细讲):
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ApiResponse<Void>> handleBadArg(IllegalArgumentException e) {
return ResponseEntity.badRequest().body(ApiResponse.fail(400, e.getMessage()));
}
}
// 效果:业务抛 IllegalArgumentException 时,前端仍收到 {"code":400,"message":"..."}
进阶:ResponseBodyAdvice 自动包装
若不想每个方法都手动包 ApiResponse,可以实现 ResponseBodyAdvice 统一在写出前包装;注意它会作用于所有 @ResponseBody 结果(包括你已手动包的),需要判断类型避免双重包装。自定义点较多,方案以官方文档为准。
小结
ResponseEntity 让「状态码、头、体」三件事在返回语句里一目了然;配合统一包装类 ApiResponse 和全局异常处理,接口的成功/失败形状就能完全一致。纯数据接口优先 @RestController + ResponseEntity,页面接口则继续用 ModelAndView 那套。