SpringBoot全局Controller返回值格式统一处理
一、Controller返回值格式统一
1、WebResult类
在 Controller对外提供服务的时候,我们都需要统一返回值格式。一般定义一个 WebResult类。
统一返回值(WebResult类)格式如下:
{"success": true,"code": 200000,"message": null,"data": {"pageList": ["张三","ccc"],"paginator": {"currentPage": 1,"pageSize": 2,"total": 3,"pages": 4}}
}
WebResult类信息:
@Data
@ApiModel(value = "Web结果集")
public class WebResult<T> implements Serializable {private static final long serialVersionUID = -4350499690382193884L;/*** 是否成功, 默认false*/@ApiModelProperty(value = "是否成功, 默认false")private Boolean success = false;/*** 返回状态码*/@ApiModelProperty(value = "返回状态码")private Integer code;/*** 返回信息*/@ApiModelProperty(value = "返回信息")private String message;/*** 返回数据*/@ApiModelProperty(value = "返回数据")private T data;public static <T> WebResult<T> ok() {WebResult<T> webResult = new WebResult<>();webResult.setSuccess(true);webResult.setCode(WebHttpCode.SERVICE_SUCCESS);webResult.setMessage("操作成功");return webResult;}public static <T> WebResult<T> ok(T data) {WebResult<T> webResult = new WebResult<>();webResult.setSuccess(true);webResult.setCode(WebHttpCode.SERVICE_SUCCESS);webResult.setMessage("操作成功");webResult.setData(data);return webResult;}public static <T> WebResult<T> error() {WebResult<T> webResult = new WebResult<>();webResult.setSuccess(false);webResult.setCode(WebHttpCode.SERVICE_ERROR);webResult.setMessage("操作失败");return webResult;}public WebResult message(String message) {this.setMessage(message);return this;}public WebResult data(T data) {this.setData(data);return this;}}
如果我们不做全局 Controller统一处理返回时,就出需要业务在每个 Controller类中返回 WebResult类。同时在全局异常中也返回 WebResult类。
有没有一种跟优雅的方式处理呢?当然有
- 业务不需要对所有 Controller都使用一个返回值(WebResult类),Controller可以需要返回原始值或者自定义的基类,然后处理器统一对返回值(WebResult类)进行封装并输出。
- 同时也可以添加自定义注解,此注解用于忽略返回值封装,按照 Controller原始值返回。
下面我们使用 HandlerMethodReturnValueHandler接口来实现。
2、相关类说明
2.1 HandlerMethodReturnValueHandler接口
使用不同策略处理从调用处理程序方法的返回值,策略处理顶层接口,自定义返回值格式需要实现此接口。
org.springframework.web.method.support.HandlerMethodReturnValueHandler
- supportsReturnType:设置支持返回值类型
- handleReturnValue:处理返回值基础参数

2.2 RequestMappingHandlerAdapter类
请求映射处理适配,包含了参数、返回值处理器等信息
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
- HandlerMethodReturnValueHandlerComposite内部维护了HandlerMethodReturnValueHandler列表

3.3 RequestResponseBodyMethodProcessor类
属于HandlerMethodReturnValueHandler子类。
org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor
- 主要功能是对请求和响应体的做处理的方法,所有属于RequestResponseBodyMethodProcessor的子类都需要替换为自定义返回值处理

全局Controller返回值统一处理实现原理就是:在bean初始化的时候,获取到所有处理器数组,然后将所有是 RequestResponseBodyMethodProcessor处理器子类对返回值处理的过程替换为自定义返回值处理器处理。
这样当调用对应返回值处理器时,将会使用到自定义的返回值处理器,也就是所有返回值都会按照规定的进行处理。
同时,我们也可以自定义注解(作用于Controller类或者方法级别忽略返回值封装),然后在自定义返回值处理器中忽略返回值封装。
二、全局Controller返回值统一处理实战
1、自定义注解
自定义注解,作用于Controller类或者方法级别忽略返回值封装。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IgnoreResponseBodyWrap {
}
2、创建自定义返回值处理器类
创建自定义处理器类实现 HandlerMethodReturnValueHandler接口,主要用于实现自定义返回值内容,不需要注入容器。
下面代码中的 BaseXXXResult是业务的基类。你可以自定义或者使用你们约定的基类。
/*** Controller 返回值格式统一处理*/
public class ResponseBodyWrapHandler implements HandlerMethodReturnValueHandler {private final HandlerMethodReturnValueHandler delegate;public ResponseBodyWrapHandler(HandlerMethodReturnValueHandler delegate) {this.delegate = delegate;}/*** 设置支持返回值类型** @param returnType* @return*/@Overridepublic boolean supportsReturnType(MethodParameter returnType) {return delegate.supportsReturnType(returnType);}/*** 处理返回值基础参数** @param returnValue 方法的返回值对象* @param returnType 封装方法参数说明的辅助类(方法的返回值类型)* @param mavContainer 用于设置模型和视图的容器* @param webRequest 当前的请求对象* @throws Exception*/@Overridepublic void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {// 如果类或者方法含有不包装注解则忽略包装IgnoreResponseBodyWrap ignoreResponseBodyWrap = returnType.getDeclaringClass().getAnnotation(IgnoreResponseBodyWrap.class);if (ignoreResponseBodyWrap != null) {delegate.handleReturnValue(returnValue, returnType, mavContainer, webRequest);return;}ignoreResponseBodyWrap = returnType.getMethodAnnotation(IgnoreResponseBodyWrap.class);if (ignoreResponseBodyWrap != null) {delegate.handleReturnValue(returnValue, returnType, mavContainer, webRequest);return;}// 返回统一格式Object obj = null;if (returnValue instanceof WebResult) {obj = returnValue;} else if (returnValue instanceof BasePageResult) {BasePageResult basePageResult = (BasePageResult) returnValue;ErrorCode errorCode = basePageResult.getErrorCode();Map<String, Object> pageData = new HashMap<>();pageData.put(CommonConstants.PAGE_LIST, basePageResult.getPageList());pageData.put(CommonConstants.PAGINATOR, basePageResult.getPaginator());obj = WebResult.ok().data(pageData).message(errorCode == null ? null : errorCode.getMessage());} else if (returnValue instanceof BaseOperateResult) {BaseOperateResult baseOperateResult = (BaseOperateResult) returnValue;ErrorCode errorCode = baseOperateResult.getErrorCode();boolean success = baseOperateResult.isSuccess();if (success) {obj = WebResult.ok().data(baseOperateResult.getId()).message(errorCode == null ? null : errorCode.getMessage());} else {obj = WebResult.error().message(errorCode == null ? null : errorCode.getMessage());}} else if (returnValue instanceof BaseDataResult) {BaseDataResult baseDataResult = (BaseDataResult) returnValue;ErrorCode errorCode = baseDataResult.getErrorCode();boolean success = baseDataResult.isSuccess();if (success) {obj = WebResult.ok().data(baseDataResult.getData()).message(errorCode == null ? null : errorCode.getMessage());} else {obj = WebResult.error().message(errorCode == null ? null : errorCode.getMessage());}} else if (returnValue instanceof BaseResult) {BaseResult baseResult = (BaseResult) returnValue;ErrorCode errorCode = baseResult.getErrorCode();boolean success = baseResult.isSuccess();if (success) {obj = WebResult.ok().message(errorCode == null ? null : errorCode.getMessage());} else {obj = WebResult.error().message(errorCode == null ? null : errorCode.getMessage());}} else {obj = returnValue;}delegate.handleReturnValue(obj, returnType, mavContainer, webRequest);}
}
3、注册自定义返回值处理器类
将所有 RequestResponseBodyMethodProcessor返回值处理器替换为自定义的返回值处理器。
@Component
public class ResponseBodyWrapFactoryBean implements InitializingBean {private final RequestMappingHandlerAdapter adapter;@Autowiredpublic ResponseBodyWrapFactoryBean(RequestMappingHandlerAdapter adapter) {this.adapter = adapter;}@Overridepublic void afterPropertiesSet() throws Exception {List<HandlerMethodReturnValueHandler> returnValueHandlers = adapter.getReturnValueHandlers();if (returnValueHandlers.size() > 0) {// 将内置的返回值处理器进行替换List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(returnValueHandlers);decorateHandlers(handlers);adapter.setReturnValueHandlers(handlers);}}/*** 将所有RequestResponseBodyMethodProcessor返回值处理器替换为自定义的返回值处理器** @author tianxincode@163.com* @since 2020/10/12*/private void decorateHandlers(List<HandlerMethodReturnValueHandler> handlers) {for (HandlerMethodReturnValueHandler handler : handlers) {if (handler instanceof RequestResponseBodyMethodProcessor) {// 替换为自定义返回值处理器ResponseBodyWrapHandler decorator = new ResponseBodyWrapHandler(handler);int index = handlers.indexOf(handler);handlers.set(index, decorator);break;}}}}
4、全局异常处理类
一般项目都会有一个全局异常处理类
@Slf4j
@ResponseBody
@ControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(value = Exception.class)private WebResult handlerException(Exception e) {log.error("Exception 异常 -> error:", e);WebResult webResult = new WebResult();webResult.setSuccess(false);webResult.setCode(WebHttpCode.SYSTEM_ERROR);webResult.setMessage("系统异常,请联系管理员");return webResult;}@ExceptionHandler(value = RuntimeException.class)private WebResult handlerRuntimeException(RuntimeException e) {log.error("RuntimeException 异常 -> error:", e);WebResult webResult = new WebResult();webResult.setSuccess(false);webResult.setCode(WebHttpCode.SYSTEM_ERROR);webResult.setMessage("系统异常");return webResult;}@ExceptionHandler(value = ServiceException.class)private WebResult handlerServiceException(ServiceException e) {log.error("ServiceException 异常 -> error:", e);WebResult webResult = new WebResult();webResult.setSuccess(false);webResult.setCode(WebHttpCode.SERVICE_ERROR);webResult.setMessage(e.getMessage());return webResult;}}
5、Controller示例
@RestController
@RequestMapping("/project2")
@Slf4j
@Api(value = "Project2Controller", tags = {"Project2相关操作接口"})
public class Project2Controller {@GetMapping("/successPage")@ApiOperation(value = "successPage接口")public BaseResult successPage() {log.info("successPage接口");List list = new ArrayList();list.add("张三");list.add("ccc");BasePageResult basePageResult = new BasePageResult<>();basePageResult.setSuccess(Boolean.TRUE);basePageResult.setPageList(list);basePageResult.setPaginator(new Paginator(1, 2, 3, 4));return basePageResult;}@GetMapping("/get")@ApiOperation(value = "get接口")@IgnoreResponseBodyWrappublic List get() {log.info("get接口");ProjectDO projectDO = new ProjectDO();projectDO.setId(10L);projectDO.setName("项目10");projectDO.setDepartmentId(0L);projectDO.setDescr("");projectDO.setDelFlag(false);projectDO.setCreateTime(new Date());projectDO.setUpdateTime(new Date());List list = new ArrayList();list.add("张三");list.add("ccc");return list;}@GetMapping("/get2")@ApiOperation(value = "get2接口")public BaseDataResult get2() {log.info("get2接口");ProjectDO projectDO = new ProjectDO();projectDO.setId(10L);projectDO.setName("项目10");projectDO.setDepartmentId(0L);projectDO.setDescr("");projectDO.setDelFlag(false);projectDO.setCreateTime(new Date());projectDO.setUpdateTime(new Date());BaseDataResult baseDataResult = new BaseDataResult();baseDataResult.setSuccess(Boolean.TRUE);baseDataResult.setData(projectDO);return baseDataResult;}/*** 文件下载接口*/@GetMapping("/download")@ApiOperation(value = "文件下载接口")public void download(HttpServletResponse response) throws SerialException {try {// 获取要下载的文件File file = new File("D:\\TempFiles\\xxxxxx.xlsx");String fileName = file.getName();response.setContentType("application/octet-stream;charset=UTF-8");response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes("UTF-8"), "iso-8859-1"));IOUtils.copy(new FileInputStream(file), response.getOutputStream());} catch (Exception e) {log.info("文件下载异常 -> e=", e);throw new ServiceException("文件下载异常");}}@GetMapping("/systemError")@ApiOperation(value = "systemError接口")public BaseResult systemError() {log.info("systemError接口");int i = 2 / 0;BaseResult baseResult = new BaseResult();baseResult.setSuccess(Boolean.TRUE);return baseResult;}}

– 求知若饥,虚心若愚。
相关文章:
SpringBoot全局Controller返回值格式统一处理
一、Controller返回值格式统一 1、WebResult类 在 Controller对外提供服务的时候,我们都需要统一返回值格式。一般定义一个 WebResult类。 统一返回值(WebResult类)格式如下: {"success": true,"code": 2…...
程序媛的mac修炼手册-- 终端shell的驾驭 zsh vs bash
进入终端(Terminal)为新下载的应用配置环境,是Mac生产力up up的关键一步,更是编程小白装大神的第一步。Fake it till you make it , 硅谷大神标准路径~ shell的基本原理 为应用配置环境,相当于在应用和操作系统间架桥。由此&…...
基于PHP的校园代购商城系统
有需要请加文章底部Q哦 可远程调试 基于PHP的校园代购商城系统 一 介绍 此校园代购商城系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。(附带参考设计文档) 技术栈:phpmysqlbootstrapphpstudyvscode 二 功能 …...
感知与认知的碰撞,大模型时代的智能文档处理范式
目录 0 写在前面1 GPT4-V:拓宽文档认知边界2 大语言模型的文档感知缺陷3 大一统文档图像处理范式3.1 像素级OCR任务3.2 OCR大一统模型3.3 长文档理解与应用 4 总结抽奖福利 0 写在前面 由中国图象图形学学会青年工作委员会发起的第十九届中国图象图形学学会青年科学…...
ECMAScript和JavaScript的区别
ECMAScript和JavaScript之间的关系和差异可以从以下几个方面来理解: 定义: ECMAScript:ECMAScript是一种由Ecma国际(前身为欧洲计算机制造商协会,英文名称是European Computer Manufacturers Association)通…...
[BUG]Datax写入数据到psql报不能序列化特殊字符
1.问题描述 Datax从mongodb写入数据到psql报错如下 org.postgresql.util.PSQLException: ERROR: invalid bytesequence for encoding "UTF8": 0x002.原因分析 此为psql独有的错误,不能对特殊字符’/u0000’,进行序列化,需要将此特殊字符替…...
用数据结构python写大数计算器
下面是一个基于Python的大数计算器的示例代码: class BigNumberCalculator:def __init__(self, num1, num2):self.num1 num1self.num2 num2staticmethoddef add(num1, num2):result carry 0len1, len2 len(num1), len(num2)max_len max(len1, len2)for i in …...
08.哲说建造者模式(Builder Pattern)
“The odds that we’re in ‘base reality’ is one in billions.” —— Elon Musk 这段话出自马斯克在2016年的一次演讲,“人类活在真实世界的几率,可能不到十亿分之一”。此言一出,可谓一石激起千层浪。有人嘲讽马斯克是“语不惊人死不休…...
ubuntu18.04查询实时内存、CPU占用率命令
gnome-system-monitor效果就是下面这样:...
Python计算圆的面积
Python 计算圆的面积 圆的面积公式为 : 公式中 r 为圆的半径。 # 定义一个方法来计算圆的面积 def findArea(r): PI 3.142 return PI * (r*r) # 调用方法 r float( input("请输入圆的半径:") ) print( "圆的面积为 %.3f&qu…...
(Java企业 / 公司项目)Nacos的怎么搭建多环境配置?(含相关面试题)(二)
上一篇讲了一个单体服务中配置,传统的Nacos配置但是在微服务架构当中肯定都是多环境下配置,比如生产环境,dev测试环境等等。 第一种方式模拟开始: 首先展示在生产环境中nacos如何配置,在模块下新建一个配置文件&…...
DolphinScheduler实际应用
前言 最近公司新启动了一个项目,然后领导想用一下新技术,并且为公司提供多个大数据调度解决方案,我呢就根据领导要求调研了下当前的开源调度工具,最终决定采用DolphinScheduler, 因此研究了一下DolphinScheduler &…...
P10 RV1126推流项目——ffmpeg输出参数初始化
前言 从本章开始我们将要学习嵌入式音视频的学习了 ,使用的瑞芯微的开发板 🎬 个人主页:ChenPi 🐻推荐专栏1: 《C_ChenPi的博客-CSDN博客》✨✨✨ 🔥 推荐专栏2: 《Linux C应用编程(概念类)_C…...
正定矩阵在格密码中的应用(知识铺垫)
目录 一. 写在前面 二. 最小值点 三. 二次型结构 四. 正定与非正定讨论 4.1 对参数a的要求 4.2 对参数c的要求 4.3 对参数b的要求 五. 最小值,最大值与奇异值 5.1 正定型(positive definite) 5.2 负定型(negative defin…...
关于使用Selenium获取网页控制台的数据
背景: 需要获取网页的控制台的数据,如下图 在此文章将使用到 Pycharm 和 Selenium4 Pycharm安装 Selenium安装 from selenium import webdriver from selenium.webdriver.common.by import By import time# 创建浏览器对象 browser webdriver.Chro…...
vue2和vue3中的路由使用及传参方式
文章目录 vue2中使用路由Vue3 中使用路由路由传参方式 Vue 2 和 Vue 3 中的路由系统有很多相似之处,但也存在一些重要的区别。下面将分别介绍 Vue 2 和 Vue 3 中的路由使用方式,并了解下它们之间的不同之处。 vue2中使用路由 在 Vue 2 中,通…...
论文管理器
论文管理器 这个论文管理器仍然存在许多漏洞。目前,通过按照一些例行程序操作,它可以正常工作。我将在有时间的时候改进代码,提供详细说明,并添加新功能。当该管理器的代码进行优化后,我会上传到github上。 一个建立…...
postfix配置tls加密
1.编译安装 编译安装openss【卸载原有openssl,然后下载新的安装,因为postfix需要新版本openssl】编译安装postfix,下面这行命令 make -f Makefile.init makefiles CCARGS"-DHAS_MYSQL -I/www/server/mysql/include -DUSE_SASL_AUTH -I/usr/include…...
虚拟专线网络(IP-VPN)
虚拟专线网络(IP-VPN),因为它的安全性和可靠性。通过亚洲领先的 IP VPN 提供商。享受更高的可管理性和可扩展性,在多个站点之间交付 IP 流量或数据包,拥有亚太地区最大的 IP 骨干网。 1,保证正常运行时间,在网络链路发…...
【Unity动画系统】Unity动画系统Animation详解,参数细节你是否弄清?
👨💻个人主页:元宇宙-秩沅 👨💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨💻 本文由 秩沅 原创 👨💻 收录于专栏:Uni…...
网络编程(Modbus进阶)
思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...
SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...
Xen Server服务器释放磁盘空间
disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...
