Spring统一数据返回格式的核心思想是:强制所有Controller的返回值遵循同一个JSON结构,从而使前端处理响应时有一致的规范。
为什么要统一数据返回格式?
- 前端处理方便:前端不需要为每个接口判断不同的响应结构,只需处理一种固定格式。
- 便于协作:前后端约定好格式后,可以并行开发。
- 标准化:包含状态码、信息、数据等标准字段,易于理解和维护。
- 易于扩展:可以统一添加如
timestamp
、path
等通用字段。
标准的统一响应体格式
一个通用的响应体通常包含以下字段:
字段名 | 类型 | 必须 | 说明 |
---|---|---|---|
code |
Integer | 是 | 业务状态码(或直接用HTTP状态码)。例如:200成功,500服务器错误。 |
message |
String | 是 | 对本次响应的提示信息,如“操作成功”、“用户不存在”等。 |
data |
Object | 否 | 返回的有效负载数据,可以是任意JSON类型(对象、数组、字符串等)。 |
timestamp |
Long | 否 | 响应时间戳(毫秒),便于调试和记录。 |
JSON示例:
成功响应:
{
"code": 200,
"message": "请求成功",
"data": {
"id": 1,
"name": "张三",
"age": 25
},
"timestamp": 1719371635110
}
失败/异常响应:
{
"code": 500,
"message": "系统内部异常,请联系管理员",
"data": null,
"timestamp": 1719371635110
}
实现方案(三种,推荐方案三)
方案一:手动封装(不推荐,繁琐)
在每个Controller方法中手动创建响应对象。
// 1. 首先定义一个通用的响应类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> {
private Integer code;
private String message;
private T data;
private Long timestamp = System.currentTimeMillis();
public static <T> Result<T> success(T data) {
return new Result<>(200, "成功", data, System.currentTimeMillis());
}
public static <T> Result<T> error(Integer code, String message) {
return new Result<>(code, message, null, System.currentTimeMillis());
}
}
// 2. 在Controller中手动使用
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/{id}")
public Result<User> getUser(@PathVariable Long id) {
User user = userService.getById(id);
return Result.success(user); // 手动包装
}
@PostMapping
public Result<String> createUser(@RequestBody User user) {
userService.save(user);
return Result.success("用户创建成功"); // 手动包装
}
}
缺点:每个方法都要写Result.success(...)
,代码重复。
方案二:使用@ControllerAdvice注解(推荐,优雅)
通过实现ResponseBodyAdvice
接口,在数据写入Response Body之前进行拦截和包装。
步骤:
- 定义统一响应体
Result
(同上)。 - 实现
ResponseBodyAdvice
:
@RestControllerAdvice(basePackages = "com.yourpackage.controller") // 指定要拦截的包
public class GlobalResponseHandler implements ResponseBodyAdvice<Object> {
/**
* 判断是否支持advice功能
* 返回true表示支持,会执行beforeBodyWrite方法
*/
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
// 排除掉已经封装成Result的返回值、Swagger的响应等
return !returnType.getParameterType().isAssignableFrom(Result.class) &&
!returnType.hasMethodAnnotation(ResponseBody.class); // 通常不需要这个检查,但根据情况定
}
/**
* 对响应体进行实际包装
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 如果返回的是String类型,需要特殊处理(因为String的转换器不同)
if (body instanceof String) {
// 通常不会直接返回String,如果返回了,需要手动转成JSON
// 实际开发中应避免Controller直接返回String
return JSON.toJSONString(Result.success(body));
}
// 如果返回体已经是Result类型(比如全局异常处理器返回的),则直接返回
if (body instanceof Result) {
return body;
}
// 最普遍的情况:包装成Result格式
return Result.success(body);
}
}
- Controller保持干净:
@RestController
@RequestMapping("/user")
public class UserController {
// 直接返回数据对象,GlobalResponseHandler会自动帮你包装成Result格式
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getById(id);
}
@PostMapping
public String createUser(@RequestBody User user) {
userService.save(user);
return "用户创建成功"; // 不推荐直接返回String,容易出问题
}
// 返回null也可以
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
userService.removeById(id);
}
}
注意:直接返回String
类型可能会引发异常,因为Spring有专门的StringHttpMessageConverter
。建议避免Controller直接返回String,或者在你的beforeBodyWrite
方法中做好特殊处理(如上例所示)。
方案三:结合全局异常处理(最完整方案)
通常,方案二会和全局异常处理一起使用,以统一成功和失败的格式。
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 捕获所有未知异常
*/
@ExceptionHandler(Exception.class)
public Result<String> handleException(Exception e) {
// 记录日志...
return Result.error(500, "系统繁忙,请稍后再试: " + e.getMessage());
}
/**
* 捕获业务异常(假设你有一个自定义的BusinessException)
*/
@ExceptionHandler(BusinessException.class)
public Result<String> handleBusinessException(BusinessException e) {
return Result.error(e.getCode(), e.getMessage());
}
}
这样,无论是正常响应还是异常抛出,最终返回给前端的都是统一的Result
格式。
总结
方案 | 优点 | 缺点 | 推荐度 |
---|---|---|---|
手动封装 | 简单直观,控制灵活 | 代码重复,容易遗漏 | ⭐⭐ |
@ControllerAdvice |
自动化,代码整洁,非侵入式 | 需要处理String类型的特殊情况 | ⭐⭐⭐⭐⭐ |
结合异常处理 | 最完整的方案,统一所有响应 | 配置稍复杂 | ⭐⭐⭐⭐⭐ |
最佳实践:采用方案三(@ControllerAdvice
+ 全局异常处理),这是目前Spring Boot项目中实现统一返回格式最主流、最优雅的方式。