统一响应与 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 那套。

笔记加载中…