千家信息网

spring boot怎么实现RESTful API返回统一数据格式

发表于:2026-11-02 作者:千家信息网编辑
千家信息网最后更新 2026年11月02日,今天就跟大家聊聊有关spring boot怎么实现RESTful API返回统一数据格式,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。关于 S
千家信息网最后更新 2026年11月02日spring boot怎么实现RESTful API返回统一数据格式

今天就跟大家聊聊有关spring boot怎么实现RESTful API返回统一数据格式,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。

关于 Spring 的全局处理,我有两方面要说:

  1. 统一数据返回格式

  2. 统一异常处理 为了将两个问题说明清楚,将分两个章节分别说明,本章主要说第一点

有童鞋说,我们项目都做了这种处理,就是在每个 API 都单独工具类将返回值进行封装,但这种不够优雅;我想写最少的代码完成这件事,也许有童鞋说,加几个注解就解决问题了,说的没错,但这篇文章主要是为了说明为什么加了几个注解就解决问题了,目的是希望大家知其所以然。

为了更好的说明问题,本文先说明如何实现,然后再详细剖析实现原理(这很关键)

为什么要做统一数据返回格式

前后端分离是当今服务形式的主流,如何设计一个好的 RESTful API ,以及如何让前端小伙伴可以处理标准的 response JSON 数据结构都至关重要,为了让前端有更好的逻辑展示与页面交互处理,每一次 RESTful 请求都应该包含以下几个信息:

名称描述
status状态码,标识请求成功与否,如 [1:成功;-1:失败]
errorCode错误码,给出明确错误码,更好的应对业务异常;请求成功该值可为空
errorMsg错误消息,与错误码相对应,更具体的描述异常信息
resultBody返回结果,通常是 Bean 对象对应的 JSON 数据, 通常为了应对不同返回值类型,将其声明为泛型类型

实现

通用返回值类定义

根据上面的描述,用 Java Bean 来体现这个结构就是这样:

@Datapublic final class CommonResult {        private int status = 1;        private String errorCode = "";        private String errorMsg = "";        private T resultBody;        public CommonResult() {        }        public CommonResult(T resultBody) {                this.resultBody = resultBody;        }}

配置

没错,我们需要借助几个关键注解来完成一下相关配置:

@EnableWebMvc@Configurationpublic class UnifiedReturnConfig {        @RestControllerAdvice("com.example.unifiedreturn.api")        static class CommonResultResponseAdvice implements ResponseBodyAdvice{                @Override                public boolean supports(MethodParameter methodParameter, Class> aClass) {                        return true;                }                @Override                public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {                        if (body instanceof CommonResult){                                return body;                        }                        return new CommonResult(body);                }        }}

到这里就结束了,我们就可以纵情的写任何 RESTful API 了,所有的返回值都会有统一的 JSON 结构

测试

新建 UserController,添加相应的 RESTful API,测试用例写的比较简单,只为了说明返回值的处理

@RestController@RequestMapping("/users")public class UserController {        @GetMapping("")        public List getUserList(){                List userVoList = Lists.newArrayListWithCapacity(2);                userVoList.add(UserVo.builder().id(1L).name("日拱一兵").age(18).build());                userVoList.add(UserVo.builder().id(2L).name("tan").age(19).build());                return userVoList;        }}

打开浏览器输入地址测试: http://localhost:8080/users/ ,我们可以看到返回了 List JSON 数据

继续添加 RESTful API,根据用户 ID 查询用户信息

@GetMapping("/{id}")public UserVo getUserByName(@PathVariable Long id){    return UserVo.builder().id(1L).name("日拱一兵").age(18).build();}

打开浏览器输入地址测试: http://localhost:8080/users/1 ,我们可以看到返回了单个 User JSON 数据

添加一个返回值类型为 ResponseEntity 的 API

@GetMapping("/testResponseEntity")public ResponseEntity getUserByAge(){    return new ResponseEntity(UserVo.builder().id(1L).name("日拱一兵").age(18).build(), HttpStatus.OK);}

打开浏览器输入地址测试: http://localhost:8080/users/testResponseEntity ,我们可以看到同样返回了单个 User JSON 数据

解剖实现过程

我会将关键部分一一说明清楚,断案还需小伙伴自己去案发现场(打开自己的 IDE 查看)

故事要从 @EnableWebMvc 这个注解说起,打开该注解看:

@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented@Import(DelegatingWebMvcConfiguration.class)public @interface EnableWebMvc {}

通过 @Import 注解引入了 DelegatingWebMvcConfiguration.class,那来看这个类吧:

@Configurationpublic class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {    ...}

有 @Configuration 注解,你应该很熟悉了,该类的父类 WebMvcConfigurationSupport 中却隐藏着一段关键代码:

@Beanpublic RequestMappingHandlerAdapter requestMappingHandlerAdapter() {    RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();    ...    return adapter;}

RequestMappingHandlerAdapter 是每一次请求处理的关键,来看该类的定义:

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter                implements BeanFactoryAware, InitializingBean {    ...}

该类实现了 InitializingBean 接口,我在 Spring Bean 生命周期之"我从哪里来"? 这篇文章中明确说明了 Spring Bean 初始化的几个关键,其中 InitializingBean 接口的 afterPropertiesSet 方法就是关键之一,在 RequestMappingHandlerAdapter 类中同样重写了该方法:

@Overridepublic void afterPropertiesSet() {    // Do this first, it may add ResponseBody advice beans    initControllerAdviceCache();    if (this.argumentResolvers == null) {        List resolvers = getDefaultArgumentResolvers();        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);    }    if (this.initBinderArgumentResolvers == null) {        List resolvers = getDefaultInitBinderArgumentResolvers();        this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);    }    if (this.returnValueHandlers == null) {        List handlers = getDefaultReturnValueHandlers();        this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);    }}

该方法内容都非常关键,但我们先来看 initControllerAdviceCache 方法,其他内容后续再单独说明:

private void initControllerAdviceCache() {        ...    if (logger.isInfoEnabled()) {        logger.info("Looking for @ControllerAdvice: " + getApplicationContext());    }    List beans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());    AnnotationAwareOrderComparator.sort(beans);    List requestResponseBodyAdviceBeans = new ArrayList();    for (ControllerAdviceBean bean : beans) {        ...        if (ResponseBodyAdvice.class.isAssignableFrom(bean.getBeanType())) {            requestResponseBodyAdviceBeans.add(bean);        }    }}

通过 ControllerAdviceBean 静态方法扫描 ControllerAdvice 注解,可是我们在实现上使用的是 @RestControllerAdvice 注解,打开看该注解:

@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@ControllerAdvice@ResponseBodypublic @interface RestControllerAdvice {

该注解由 @ControllerAdvice 和 @ResponseBody 标记,就好比你熟悉的 @RestController 注解由 @Controller 和 @ResponseBody 标记是一样的

到这里你已经知道我们用 @RestControllerAdvice 标记的 Bean 是如何被加载到 Spring 上下文的,接下来就要知道是 Spring 是如何使用我们的 bean 以及对返回 body 做处理的

其实在 HttpMessageConverter是如何转换数据的? 这篇文章中已经说明了一部分,希望小伙伴先看这篇文章,下面的部分就会秒懂了,我们在这里做进一步的说明

在 AbstractMessageConverterMethodProcessor 的 writeWithMessageConverters 方法中,有一段核心代码:

if (messageConverter instanceof GenericHttpMessageConverter) {    if (((GenericHttpMessageConverter) messageConverter).canWrite(            declaredType, valueType, selectedMediaType)) {        outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,                (Class>) messageConverter.getClass(),                inputMessage, outputMessage);            ...        return;    }}

可以看到通过 getAdvice() 调用了 beforeBodyWrite 方法,我们已经接近真相了

protected RequestResponseBodyAdviceChain getAdvice() {    return this.advice;}

RequestResponseBodyAdviceChain,看名字带有 Chain,很明显用到了「责任链设计模式」,这些内容在 不得不知的责任链设计模式 文章中明确说明过,只不过它传递责任链以循环的方式完成:

class RequestResponseBodyAdviceChain implements RequestBodyAdvice, ResponseBodyAdvice {    @Override        public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType contentType,                        Class> converterType,                        ServerHttpRequest request, ServerHttpResponse response) {                return processBody(body, returnType, contentType, converterType, request, response);        }    @SuppressWarnings("unchecked")        private  Object processBody(Object body, MethodParameter returnType, MediaType contentType,                        Class> converterType,                        ServerHttpRequest request, ServerHttpResponse response) {                for (ResponseBodyAdvice advice : getMatchingAdvice(returnType, ResponseBodyAdvice.class)) {                        if (advice.supports(returnType, converterType)) {                                body = ((ResponseBodyAdvice) advice).beforeBodyWrite((T) body, returnType,                                                contentType, converterType, request, response);                        }                }                return body;        }}

我们重写的 beforeBodyWrite 方法终究会被调用到,真相就是这样了!!!

其实还没完,你有没有想过,如果我们的 API 方法返回值是 org.springframework.http.ResponseEntity 类型,我们可以指定 HTTP 返回状态码,但是这个返回值会直接放到我们的 beforeBodyWrite 方法的 body 参数中吗?如果这样做很明显是错误的,因为 ResponseEntity 包含很多我们非业务数据在里面,那 Spring 是怎么帮我们处理的呢?

在我们方法取得返回值并且在调用 beforeBodyWrite 方法之前,还要选择 HandlerMethodReturnValueHandler 用于处理不同的 Handler 来处理返回值

在类 HandlerMethodReturnValueHandlerComposite 中的 handleReturnValue 方法中

@Overridepublic void handleReturnValue(Object returnValue, MethodParameter returnType,        ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);    if (handler == null) {        throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());    }    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);}

通过调用 selectHandler 方法来选择合适的 handler,Spring 内置了很多个 Handler,我们来看类图:

HttpEntityMethodProcessor 就是其中之一,它重写了 supportsParameter 方法,支持 HttpEntity 类型,即支持 ResponseEntity 类型:

@Overridepublic boolean supportsParameter(MethodParameter parameter) {    return (HttpEntity.class == parameter.getParameterType() ||            RequestEntity.class == parameter.getParameterType());}

所以当我们返回的类型为 ResponseEntity 时,就要通过 HttpEntityMethodProcessor 的 handleReturnValue 方法来处理我们的结果:

@Overridepublic void handleReturnValue(Object returnValue, MethodParameter returnType,        ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {    ...    if (responseEntity instanceof ResponseEntity) {        int returnStatus = ((ResponseEntity) responseEntity).getStatusCodeValue();        outputMessage.getServletResponse().setStatus(returnStatus);        if (returnStatus == 200) {            if (SAFE_METHODS.contains(inputMessage.getMethod())                    && isResourceNotModified(inputMessage, outputMessage)) {                // Ensure headers are flushed, no body should be written.                outputMessage.flush();                // Skip call to converters, as they may update the body.                return;            }        }    }    // Try even with null body. ResponseBodyAdvice could get involved.    writeWithMessageConverters(responseEntity.getBody(), returnType, inputMessage, outputMessage);    // Ensure headers are flushed even if no body was written.    outputMessage.flush();}

该方法提取出 responseEntity.getBody(),并传递个 MessageConverter,然后再继续调用 beforeBodyWrite 方法,这才是真相!!!

看完上述内容,你们对spring boot怎么实现RESTful API返回统一数据格式有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注行业资讯频道,感谢大家的支持。

方法 注解 处理 数据 关键 类型 统一 内容 就是 篇文章 错误 测试 格式 问题 成功 代码 信息 地址 小伙 小伙伴 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 互联网科技公司 互联网科技革命 奔汉互联网科技 左右互联网络科技 甘肃拓世互联网科技有限公司 云创客互联网科技公司 须眉科技是互联网公司吗 广州慧亚互联网科技怎么样 顶级互联网科技成果有哪些 宁夏科技产业互联网案例 智腾互联网科技有限公司 速维有品互联网信息科技(苏州)有限公司 亳州好赞互联网科技有限公司 广州新华互联网科技学校电竞方面 下一代互联网重大应用科技园 江苏互联网科技小贷 江苏互联网智能科技产品出厂价 互联网领先科技活动 安徽零酒互联网科技有限公司 互联网科技博主张洲 互联网金融监管科技数据 上海致骋互联网科技 融势互联网科技 互联网金融和金融科技 厦门比较好的科技互联网公司 互联网与医疗科技 互联网新科技动漫ppt素材 电子商务与互联网科技的关系 云南互联网科技学费 兰石爱特工业互联网科技分公司
0