SpringBoot统一数据返回格式 统一异常处理

统一数据返回格式 & 统一异常处理

  • 1. 统一数据返回格式
    • 1.1 快速入门
    • 1.2 存在问题
    • 1.3 案列代码修改
    • 1.4 优点
  • 2. 统一异常处理

1. 统一数据返回格式

强制登录案例中,我们共做了两部分⼯作

  1. 通过Session来判断⽤⼾是否登录
  2. 对后端返回数据进⾏封装,告知前端处理的结果

回顾

后端统⼀返回结果

@Data
public class Result<T> {private int status;private String errorMessage;private T data;
}

后端逻辑处理

@RequestMapping("/getListByPage")
public Result getListByPage(PageRequest pageRequest) {log.info("获取图书列表, pageRequest:{}", pageRequest);//⽤⼾登录, 返回图书列表 PageResult<BookInfo> pageResult =bookService.getBookListByPage(pageRequest);log.info("获取图书列表222, pageRequest:{}", pageResult);return Result.success(pageResult);
}

Result.success(pageResult)就是对返回数据进⾏了封装

拦截器帮我们实现了第⼀个功能,接下来看SpringBoot对第⼆个功能如何⽀持

1.1 快速入门

统⼀的数据返回格式使⽤@ControllerAdvice 和ResponseBodyAdvice 的⽅式实现 @ControllerAdvice 表⽰控制器通知类

添加类 ResponseAdvice ,实现 ResponseBodyAdvice 接⼝,并在类上添加 @ControllerAdvice 注解

import com.example.demo.model.Result;
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;
importorg.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType,MediaType selectedContentType, Class selectedConverterType, ServerHttpRequestrequest, ServerHttpResponse response) {return Result.success(body);}
}
  • supports⽅法:判断是否要执⾏beforeBodyWrite⽅法.true为执⾏,false不执⾏.通过该⽅法可以 选择哪些类或哪些⽅法的response要进⾏处理,其他的不进⾏处理.

从returnType获取类名和⽅法名

//获取执⾏的类 
Class<?> declaringClass = returnType.getMethod().getDeclaringClass();//获取执⾏的⽅法 Method method = returnType.getMethod();
  • beforeBodyWrite⽅法:对response⽅法进⾏具体操作处理

测试

测试接⼝:http://127.0.0.1:8080/book/queryBookById?bookId=1

添加统⼀数据返回格式之前:


添加统一数据返回格式之后:

1.2 存在问题

我们继续测试修改图书的接口: http://127.0.0.1:8080/book/updateBook

在这里插入图片描述

结果显示, 发生内部错误
查看数据库, 发现数据库操作成功

查看日志, 日志报错:


多测试几种不同的返回结果, 发现只有返回结果为 String类型时才有这种错误发生.
测试代码:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/test")
@RestController
public class TestController {@RequestMapping("/t1")public String t1(){return "t1";}@RequestMapping("/t2")public boolean t2(){return true;}@RequestMapping("/t3")public Integer t3(){return 200;}
}

解决方案:

import com.example.demo.model.Result;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
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;
@Slf4j
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {private static ObjectMapper mapper = new ObjectMapper();@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@SneakyThrows@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType,MediaType selectedContentType, Class selectedConverterType, ServerHttpRequestrequest, ServerHttpResponse response) {//如果返回结果为String类型, 使⽤SpringBoot内置提供的Jackson来实现信息的序列化if (body instanceof String){return mapper.writeValueAsString(Result.success(body));}return Result.success(body);}
}

重新测试, 结果返回正常:

原因分析:
SpringMVC默认会注册⼀些⾃带的 HttpMessageConverter

(从先后顺序排列分别为 ByteArrayHttpMessageConverter ,StringHttpMessageConverter , SourceHttpMessageConverter , SourceHttpMessageConverter ,AllEncompassingFormHttpMessageConverter )

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {//...public RequestMappingHandlerAdapter() {this.messageConverters = new ArrayList<>(4);this.messageConverters.add(new ByteArrayHttpMessageConverter());this.messageConverters.add(new StringHttpMessageConverter());if (!shouldIgnoreXml) {try {this.messageConverters.add(new SourceHttpMessageConverter<>());}catch (Error err) {// Ignore when no TransformerFactory implementation is available}}this.messageConverters.add(newAllEncompassingFormHttpMessageConverter());}//...
}

其中AllEncompassingFormHttpMessageConverter会根据项⽬依赖情况添加对应的 HttpMessageConverter

public AllEncompassingFormHttpMessageConverter() {if (!shouldIgnoreXml) {try {addPartConverter(new SourceHttpMessageConverter<>());}catch (Error err) {// Ignore when no TransformerFactory implementation is available}if (jaxb2Present && !jackson2XmlPresent) {addPartConverter(new Jaxb2RootElementHttpMessageConverter());}}if (kotlinSerializationJsonPresent) {addPartConverter(new KotlinSerializationJsonHttpMessageConverter());}if (jackson2Present) {addPartConverter(new MappingJackson2HttpMessageConverter());}else if (gsonPresent) {addPartConverter(new GsonHttpMessageConverter());}else if (jsonbPresent) {addPartConverter(new JsonbHttpMessageConverter());}if (jackson2XmlPresent && !shouldIgnoreXml) {addPartConverter(new MappingJackson2XmlHttpMessageConverter());}if (jackson2SmilePresent) {addPartConverter(new MappingJackson2SmileHttpMessageConverter());}
}

在依赖中引⼊jackson包后,容器会把 MappingJackson2HttpMessageConverter ⾃动注册到 messageConverters 链的末尾.

Spring会根据返回的数据类型,从 messageConverters 链选择合适的 HttpMessageConverter .

当返回的数据是⾮字符串时,使⽤的 MappingJackson2HttpMessageConverter 写⼊返回对象. 当返回的数据是字符串时, StringHttpMessageConverter 会先被遍历到,这时会认为 StringHttpMessageConverter 可以使⽤.

public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver implements HandlerMethodReturnValueHandler {//...代码省略protected <T> void writeWithMessageConverters(@Nullable T value,MethodParameter returnType,ServletServerHttpRequest inputMessage, ServletServerHttpResponseoutputMessage)throws IOException, HttpMediaTypeNotAcceptableException,HttpMessageNotWritableException {//...代码省略if (selectedMediaType != null) {selectedMediaType = selectedMediaType.removeQualityValue();for (HttpMessageConverter<?> converter : this.messageConverters) {GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ?(GenericHttpMessageConverter<?>) converter : null);if (genericConverter != null ?((GenericHttpMessageConverter)converter).canWrite(targetType, valueType, selectedMediaType) :converter.canWrite(valueType, selectedMediaType)) {//getAdvice().beforeBodyWrite 执⾏之后, body转换成了Result类型的结果body = getAdvice().beforeBodyWrite(body, returnType,selectedMediaType,(Class<? extends HttpMessageConverter<?>>) converter.getClass(), inputMessage, outputMessage);if (body != null) {Object theBody = body;LogFormatUtils.traceDebug(logger, traceOn ->"Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]");addContentDispositionHeader(inputMessage, outputMessage);if (genericConverter != null) {genericConverter.write(body, targetType, selectedMediaType, outputMessage);}else {//此时cover为StringHttpMessageConverter ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);}}else {if (logger.isDebugEnabled()) {logger.debug("Nothing to write: null body");}}return;}}}//...代码省略 }//...代码省略 
}

在 ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage) 的处理中,调⽤⽗类的write⽅法

由于 StringHttpMessageConverter 重写了addDefaultHeaders⽅法,所以会执⾏⼦类的⽅法


然⽽⼦类 StringHttpMessageConverter 的addDefaultHeaders⽅法定义接收参数为String,此 时t为Result类型,所以出现类型不匹配"Resultcannotbecasttojava.lang.String"的异常

1.3 案列代码修改

如果⼀些⽅法返回的结果已经是Result类型了,那就直接返回Result类型的结果即可

@SneakyThrows
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType,MediaType selectedContentType, Class selectedConverterType, ServerHttpRequestrequest, ServerHttpResponse response) {//返回结果更加灵活 if (body instanceof Result){return body;}//如果返回结果为String类型, 使⽤SpringBoot内置提供的Jackson来实现信息的序列化 if (body instanceof String){return mapper.writeValueAsString(Result.success(body));}return Result.success(body);
}

1.4 优点

  1. ⽅便前端程序员更好的接收和解析后端数据接⼝返回的数据
  2. 降低前端程序员和后端程序员的沟通成本,按照某个格式实现就可以了,因为所有接⼝都是这样返回 的.
  3. 有利于项⽬统⼀数据的维护和修改.
  4. 有利于后端技术部⻔的统⼀规范的标准制定,不会出现稀奇古怪的返回内容.

2. 统一异常处理

统⼀异常处理使⽤的是@ControllerAdvice +@ExceptionHandler 来实现的, @ControllerAdvice 表⽰控制器通知类, @ExceptionHandler 是异常处理器,两个结合表 ⽰当出现异常的时候执⾏某个通知,也就是执⾏某个⽅法事件.

具体代码如下:

import com.example.demo.model.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
@ResponseBody
public class ErrorAdvice {@ExceptionHandlerpublic Object handler(Exception e) {return Result.fail(e.getMessage());}
}

类名,⽅法名和返回值可以⾃定义,重要的是注解

接⼝返回为数据时,需要加 @ResponseBody 注解

以上代码表⽰,如果代码出现Exception异常(包括Exception的⼦类),就返回⼀个Result的对象,Result 对象的设置参考Result.fail(e.getMessage())

public static Result fail(String msg) {Result result = new Result();result.setStatus(ResultStatus.FAIL);result.setErrorMessage(msg);result.setData("");return result;
}

我们可以针对不同的异常,返回不同的结果.

import com.example.demo.model.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ResponseBody
@ControllerAdvice
public class ErrorAdvice {@ExceptionHandlerpublic Object handler(Exception e) {return Result.fail(e.getMessage());}@ExceptionHandlerpublic Object handler(NullPointerException e) {return Result.fail("发⽣NullPointerException:"+e.getMessage());}@ExceptionHandlerpublic Object handler(ArithmeticException e) {return Result.fail("发⽣ArithmeticException:"+e.getMessage());}
}

模拟制造异常:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/test")
@RestController
public class TestController {@RequestMapping("/t1")public String t1(){return "t1";}@RequestMapping("/t2")public boolean t2(){int a = 10/0; //抛出ArithmeticExceptionreturn true;}@RequestMapping("/t3")public Integer t3(){String a =null;System.out.println(a.length()); //抛出NullPointerExceptionreturn 200;}
}

当有多个异常通知时,匹配顺序为当前类及其⼦类向上依次匹配

/test/t2抛出ArithmeticException,运⾏结果如下:

/test/t3抛出NullPointerException,运⾏结果如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/68250.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Elasticsearch+kibana安装(简单易上手)

下载ES( Download Elasticsearch | Elastic ) 将ES安装包解压缩 解压后目录如下: 修改ES服务端口&#xff08;可以不修改&#xff09; 启动ES 记住这些内容 验证ES是否启动成功 下载kibana( Download Kibana Free | Get Started Now | Elastic ) 解压后的kibana目…

十年筑梦,再创鲸彩!庆祝和鲸科技十周年

2025 年 1 月 16 日&#xff0c;“十年筑梦&#xff0c;再创鲸彩” 2025 和鲸科技十周年庆暨 2024 年终表彰大会圆满落幕。 十年征程&#xff0c;和鲸科技遨游于科技蓝海&#xff0c;破浪前行&#xff0c;无惧风雨。期间所取得的每一项成就&#xff0c;都凝聚着全体成员的智慧结…

【Uniapp-Vue3】动态设置页面导航条的样式

1. 动态修改导航条标题 uni.setNavigationBarTitle({ title:"标题名称" }) 点击修改以后顶部导航栏的标题会从“主页”变为“动态标题” 2. 动态修改导航条颜色 uni.setNavigationBarColor({ backgroundColor:"颜色" }) 3. 动态添加导航加载动画 // 添加加…

openlayer getLayerById 根据id获取layer图层

背景&#xff1a; 在项目中使用getLayerById获取图层&#xff0c;这个getLayerById()方法不是openlayer官方文档自带的&#xff0c;而是自己封装的一个方法&#xff0c;这个封装的方法的思路是&#xff1a;遍历所有的layer&#xff0c;根据唯一标识【可能是id&#xff0c;也可能…

Unity入门2 背景叠层 瓦片规则

切割场景 瓦片调色盘 放在Assets里面新建瓦片地图,palettes tile 瓦片 palettes调色板 上下窗口是分开的 拖进这个格子窗 瓦片太碎&#xff0c;要封装 装好之后&#xff0c;只是把瓦片放上去了&#xff0c;但是还没有画布&#xff0c;显示是这样的 no valid target 新建“…

Kafka 日志存储 — 日志清理

Kafka 提供两种日志清理策略&#xff1a;日志清理(Log Delete)与日志压缩(Log Compaction)。 1 日志清理 通过broker端参数log.cleanup.policy来设置日志清理策略&#xff0c;默认值为“delete”。如果要采用日志压缩的清理策略&#xff0c;则设置为“compact”。可以同时支持…

Semantic Kernel - Kernel理解

目录 一、关于Kernel 二、案例实战 三、运行截图 一、关于Kernel 微软的 Semantic Kernel 项目中,Semantic Kernel 是一个工具框架,旨在使得开发人员能够更容易地将大语言模型(如GPT)集成到不同的应用中。它通过提供一组接口、任务模板和集成模块,使开发者能够轻松地设计…

人格分裂(交互问答)-小白想懂Elasticsearch

通过交互式追问了解一个中间件 ? 啥是Elasticsearch ! 分布式搜索和分析引擎 ? 为啥是分布式搜索&#xff0c;单体难道用不了吗 ? 实际上是说这个东西可以分布式部署 ! 单机可用但扩展性差&#xff0c;分布式通过分片、副本和负载均衡实现海量数据存储与高并发处理 ? 提…

golang 使用双向链表作为container/heap的载体

MyHeap&#xff1a;container/heap的数据载体&#xff0c;需要实现以下方法&#xff1a; Len&#xff1a;堆中数据个数 Less&#xff1a;第i个元素 是否必 第j个元素 值小 Swap&#xff1a;交换第i个元素和 第j个元素 Push&#xff1a;向堆中追加元素 Pop&#xff1a;从堆…

svn: E000111: Error running context: Connection refused

1、具体报错&#xff1a; 看起来是window主机的子系统ubuntu svn客户端无法访问到window主机的svn的服务端。 2、问题&#xff1a; window主机安装子系统ubuntu&#xff0c;ubuntu是可以直接访问外网&#xff0c;但是ubuntu是不能访问window主机的服务&#xff0c;比如svn的se…

PostgreSQL TRUNCATE TABLE 操作详解

PostgreSQL TRUNCATE TABLE 操作详解 引言 在数据库管理中,经常需要对表进行操作以保持数据的有效性和一致性。TRUNCATE TABLE 是 PostgreSQL 中一种高效删除表内所有记录的方法。本文将详细探讨 PostgreSQL 中 TRUNCATE TABLE 的使用方法、性能优势以及注意事项。 什么是 …

【时时三省】(C语言基础)对比一组函数

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 对比一组函数 比如对比一下scanf fscanf sscanf和printf fprintf sprintf scanf 针对标准输入的格式化的输入语句 其实它针对的是stdin fscanf 针对所有输入流的格式化的输入语句 它是针对s…

音频 PCM 格式 - raw data

文章目录 raw 音频格式&#xff1a;PCM其他音频格式&#xff1a;mp31. 无损压缩音频&#xff08;类比 PNG 图像&#xff09;2. 有损压缩音频&#xff08;类比 JPEG 图像&#xff09; 试了一下科大讯飞的音频识别云 api&#xff0c;踩了点坑 与本文无关&#xff1a;讯飞的 api 使…

(MySQL)头歌数据库作业答案

1.数据库和表的基本操作 1.1数据库和表的基本操作&#xff08;一&#xff09; 第1关&#xff1a;查看表结构与修改表名 本关任务&#xff1a;修改表名&#xff0c;并能顺利查询到修改后表的结构。 USE Company;#请在此处添加实现代码 ########## Begin ###################…

计算机网络 (57)改进“尽最大努力交付”的服务

前言 计算机网络中的“尽最大努力交付”服务是网络层的一种数据传输方式。这种服务的特点是网络层只负责尽力将数据报从源端传输到目的端&#xff0c;而不保证数据传输的可靠性。 一、标记与分类 为数据分组打上标记&#xff1a; 给不同性质的分组打上不同的标记&#x…

Day37:添加元素到列表中

在 Python 中&#xff0c;列表&#xff08;List&#xff09;是一种非常常用的数据结构&#xff0c;可以存储多个元素。我们经常需要向列表中添加元素。Python 提供了多种方法来向列表中添加元素&#xff0c;最常用的方法是 append()、insert() 和 extend()。 1. 使用 append() …

Nginx前端后端共用一个域名如何配置

在 Nginx 中配置前端和后端共用一个域名的情况&#xff0c;通常是通过路径或子路径将请求转发到不同的服务。以下是一个示例配置&#xff0c;假设&#xff1a; 前端静态文件在 /var/www/frontend/。 后端 API 服务运行在 http://127.0.0.1:5000。 域名是 example.com&#xff…

Flutter子页面向父组件传递数据方法

在 Flutter 中&#xff0c;如果父组件需要调用子组件的方法&#xff0c;可以通过以下几种方式实现。以下是常见的几种方法&#xff1a; 方法 1&#xff1a;使用 GlobalKey 和 State 调用子组件方法 这是最直接的方式&#xff0c;通过 GlobalKey 获取子组件的 State&#xff0c…

Direct Preference Optimization (DPO): 一种无需强化学习的语言模型偏好优化方法

论文地址&#xff1a;https://arxiv.org/pdf/2305.18290 1. 背景与挑战 近年来&#xff0c;大规模无监督语言模型&#xff08;LM&#xff09;在知识获取和推理能力方面取得了显著进展&#xff0c;但如何精确控制其行为仍是一个难题。 现有的方法通常通过**强化学习从人类反馈&…

ppp综合实验

IP地址 r1 r2 r3 r4 hdlc封装 pap认证 r2 r3 chap认证 r2 r4 MGRE 主认证 [r1]int Tunnel 0/0/0 [r1-Tunnel0/0/0]ip add 192.168.4.1 24 [r1-Tunnel0/0/0]tunnel-protocol gre p2mp [r1-Tunnel0/0/0]source 12.1.1.1 [r1-Tunnel0/0/0]nhrp entry multicast dynamic [r1-Tu…