SpringBoot 统一接口数据返回格式实战
在开发过程中,如果每个控制器的返回值格式都不一致,后续前端对接和维护都会非常麻烦。有没有办法在不修改每个方法的前提下统一格式?答案是肯定的,利用 Spring Boot 提供的 @ControllerAdvice 和 ResponseBodyAdvice 机制即可实现全局拦截。
核心实现思路
定义一个类实现 ResponseBodyAdvice 接口,并加上 @ControllerAdvice 注解。这样可以在响应体写入前统一处理数据。
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 如果已经是 Result 类型,直接返回
if (body instanceof Result) {
return body;
}
// 否则包装成统一的成功响应
return Result.success(body);
}
}
配置完成后,普通的 Integer、Boolean 等类型都能正常被包装成 Result 结构。但在实际测试中,我们发现返回类型为 String 时会出现异常。
为什么 String 类型会报错?
当返回值为 String 时,Spring MVC 的消息转换器(HttpMessageConverter)处理逻辑会有所不同。StringHttpMessageConverter 对响应头有特定要求,它期望处理的是纯文本。而我们的拦截器试图将 String 转换为 Result 对象,这导致在后续的序列化阶段,StringHttpMessageConverter 无法正确处理自定义的 Result 类型,从而抛出 ClassCastException。
简单来说,就是消息转换器的优先级和类型匹配在 String 场景下产生了冲突。
解决方案:手动序列化
要解决这个问题,我们需要在拦截器内部对 String 类型做特殊处理,先将其序列化为 JSON 字符串,再包装进 Result。这需要引入 Jackson 的 ObjectMapper。
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import springbook.model.Result;
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
@Autowired
private ObjectMapper objectMapper;
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
if (body instanceof Result) {
return body;
}
// 针对 String 类型,先转为 JSON 字符串再包装
if (body instanceof String) {
return objectMapper.writeValueAsString(Result.success(body));
}
return Result.success(body);
}
}
经过上述调整,无论返回何种类型,最终接口都会返回统一的 JSON 结构。
这样做的好处
统一返回格式不仅仅是为了好看,更是工程化的需要。它能显著降低前后端的沟通成本,因为双方只需约定一种数据结构;同时也方便后端维护,一旦需要增加错误码或时间戳,只需修改拦截器一处即可生效,避免了散落在各个业务方法中的重复代码。

