7.11 SpringBoot实战 全局异常处理 - 深入细节详解

CSDN成就一亿技术人

文章目录

  • 前言
  • 一、异常分类
    • 1.1 业务异常
    • 1.2 参数校验异常
    • 1.3 通用异常兜底
  • 二、保留异常现场
    • 2.1 请求地址
    • 2.2 请求header
    • 2.3 请求参数+body
    • 2.4 构建异常上下文消息
  • 最后


前言

全局异常处理, 你真的学会了吗?

学完上文,你有思考和动手实践吗?

上文咱们主要讲的是全局异常处理机制,说句实在话,如果没有人带你,即使你掌握了机制,也未必能玩转异常处理!异常处理真的很重要,所以本文带大家在图书实战项目中落地!非常深入,非常细节,非常详细!你绝对没看过这么全的,最后有源码齐全可直接Copy!

我们的重点是利用全局异常处理机制来为我们好好服务,达到异常为我、我爱异常

上文地址:7.10 SpringBoot实战 全局异常处理


一、异常分类

对于@ExceptionHandler,如果你只定义一个@ExceptionHandler(Exception.class)未免过于粗!

但是,如果你把所有异常都加一个@ExceptionHandler,又未免过于太细!没有必要!

所以,我们将需要【独立解析的异常】归为一类,统一处理!

1.1 业务异常

这里说的业务异常,不是JDK或第三方类库封装的异常类,而是由你自定义,并由你主动抛出的异常,可能是一个,也可能是N个,具体取决于你业务的复杂度!

本项目目前只需要先定义一个业务异常:BizException

我们在业务逻辑校验不通过时,统一抛出该异常,并且统一在全局异常处理该异常!

这正是我对于【7.1】中如何优雅处理的答案!你懂了吗? 7.1「实战」图书录入和修改API --如何优雅处理校验逻辑?

在这里插入图片描述

因为BizException可能在项目中任意地方抛出,所以需要将此类定义在common

注意, 业务异常是在运行时由我们主动抛出,属于运行时异常,所以继承自RuntimeException

/*** 业务异常类** @author 天罡gg* @date 2023/8/27**/
public class BizException extends RuntimeException {private String code;public BizException(String message) {this("400", message);}public BizException(String message, Throwable cause) {this("400", message, cause);}public BizException(String code, String message) {super(message);this.code = code;}public BizException(String code, String message, Throwable cause) {super(message, cause);this.code = code;}public String getCode() {return this.code;}public void setCode(String code) {this.code = code;}
}

上面这些代码比较基础,message在父类已定义,所以主要定义了一个code,并实现了4个构造函数重载,以适用于不同的业务场景调用!

你可以根据你的业务定义不同的BizException,增加不同的参数!

然后,我们在GlobalExceptionHandler中通过@ExceptionHandler(BizException.class)通用处理!

@ExceptionHandler(BizException.class)
public TgResult handleBizException(BizException e) {log.warn("BizException", e);return TgResult.fail(e.getCode(), e.getMessage());
}

1.2 参数校验异常

除了业务异常,通常还有一类必须处理的异常:参数校验异常!

在springboot中,在controller层通常都是基于注解的参数校验!这部分目前我们还没有在项目中应用,这是不够健壮性的,所以在后面也会安排讲这部分!我们先处理校验失败抛出的异常!

校验失败会抛出:BindExceptionMethodArgumentNotValidException,至于为什么不做展开!

@ExceptionHandler(BindException.class)
public TgResult handleBindException(BindException e) {StringBuilder sb = new StringBuilder();e.getBindingResult().getAllErrors().forEach(error -> {sb.append(error.getDefaultMessage()).append("\r\n");});log.warn("BindException:{}", sb, e);return TgResult.fail("400", sb.toString());
}@ExceptionHandler(MethodArgumentNotValidException.class)
public TgResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {StringBuilder sb = new StringBuilder();e.getBindingResult().getAllErrors().forEach(error -> {sb.append(error.getDefaultMessage()).append("\r\n");});log.warn("MethodArgumentNotValidException:{}", sb, e);return TgResult.fail("400", sb.toString());
}

1.3 通用异常兜底

这个兜底就是我们上文加过的@ExceptionHandler(Exception.class),所有异常通吃,所以用这个兜底!

本文以此3类抛转引玉,相信能解决大部分场景!如果超出处理范围,原则是当你发现通过@ExceptionHandler(Exception.class)无法解析出想要的信息时,就可以定义新的@ExceptionHandler(XXX.class)!


二、保留异常现场

解决BUG就像破案一样,通过异常反推,总有一些诡异的异常,绞尽脑汁,让你想破了天,可能依然摸不着头脑,但是如果测试人员能够复现,那么你解决起来就会水到渠成!认同的,点个赞 (≧▽≦)/

那么如何才能不依赖测试人员,只靠自己就能复现问题呢?

今天再教你实用一招,让你以后Happy的解决异常,那就是保留好异常现场,或者说是现场还原!

难的不会,会的不难,主要使用 HttpServletRequest 记录这一次Http请求的3大部分:请求地址、请求header、请求参数

实际上,在@RestControllerAdvice中,我们依然可以在@ExceptionHandler修饰的方法参数上加入HttpServletRequest,例如:

在这里插入图片描述

2.1 请求地址

  • 获取API的请求地址request.getRequestURI()

  • 获取API的请求方法通过:request.getMethod()

2.2 请求header

  • 获取指定header的值:request.getHeader

    规范的程序,我们在请求报文中定义的header都是固定的,所以只需要按header来获取值即可!

    例如本项目有个header叫tgCsrfToken,就这样获取:

    `request.getHeader("tgCsrfToken")`
    
  • 获取全部header:request.getHeaderNames

    Enumeration<String> headers = request.getHeaderNames();
    StringBuilder sbAllHeaders = new StringBuilder();
    sbAllHeaders.append("headers:\r\n");
    while (headers.hasMoreElements()) {String headerKey = headers.nextElement();String headerValue = request.getHeader(headerKey);sbAllHeaders.append(headerKey+":"+headerValue+"\r\n");
    }
    

2.3 请求参数+body

  • 获取拼接地址上的参数:request.getParameterMap()
  • 获取body的参数:request.getReader()

不过此时使用getReader()会报异常:getInputStream() has already been called for this request

原因是因为流总是向前的,只可以读取一次,所以要反复使用,需提前缓存body,以达到反复使用的目的。

解决方案是使用Filter,在doFilter时传入我们缓存的的HttpServletRequestWrapper,具体的实现:

CacheBodyFilter,优先级最高的过滤器、只执行一次,== 目的是将HttpServletRequest包装成CacheBodyHttpServletRequestWrapper ==

@Order(value = Ordered.HIGHEST_PRECEDENCE)
@WebFilter(filterName = "CacheBodyFilter", urlPatterns = "/*")
@Component
public class CacheBodyFilter extends OncePerRequestFilter {@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {CacheBodyHttpServletRequestWrapper servletRequest = new CacheBodyHttpServletRequestWrapper(httpServletRequest);filterChain.doFilter(servletRequest, httpServletResponse);}
}

CacheBodyHttpServletRequestWrapper 缓存body

public class CacheBodyHttpServletRequestWrapper extends HttpServletRequestWrapper {private final byte[] body;public CacheBodyHttpServletRequestWrapper(HttpServletRequest request) throws IOException {super(request);InputStream requestInputStream = request.getInputStream();this.body = StreamUtils.copyToByteArray(requestInputStream);}@Overridepublic ServletInputStream getInputStream() throws IOException {return new CacheBodyServletInputStream(this.body);}@Overridepublic BufferedReader getReader() throws IOException {ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.body);return new BufferedReader(new InputStreamReader(byteArrayInputStream));}public byte[] getBody() {return body;}public static class CacheBodyServletInputStream extends ServletInputStream {private final InputStream cacheBodyInputStream;public CacheBodyServletInputStream(byte[] cachedBody) {this.cacheBodyInputStream = new ByteArrayInputStream(cachedBody);}@Overridepublic int read() throws IOException {return cacheBodyInputStream.read();}@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return true;}@Overridepublic void setReadListener(ReadListener readListener) {}}
}

2.4 构建异常上下文消息

如何获取都有了,那么我们加一个方法来构建消息吧~

/*** 构建异常上下文消息**/
private String buildContextMessage(HttpServletRequest request) {// 请求地址String url = request.getRequestURI();String method = request.getMethod();// 获取指定header// String oneHeader = request.getHeader("tgCsrfToken");// 获取全部headerEnumeration<String> allHeaders = request.getHeaderNames();StringBuilder sbAllHeaders = new StringBuilder();while (allHeaders.hasMoreElements()) {String headerKey = allHeaders.nextElement();String headerValue = request.getHeader(headerKey);sbAllHeaders.append(headerKey).append(":").append(headerValue).append("\r\n");}// 请求参数String parameterMap = request.getParameterMap().toString();// 获取bodyString body = null;if (request instanceof CacheBodyHttpServletRequestWrapper) {CacheBodyHttpServletRequestWrapper wrapper = (CacheBodyHttpServletRequestWrapper) request;body = new String(wrapper.getBody());}return String.format("url:%s, method:%s, headers:%s, parameterMap:%s, body:%s", url, method, sbAllHeaders.toString(), parameterMap, body);
}

最终调用的完整代码如下:

// 业务异常 ===========================================
@ExceptionHandler(BizException.class)
public TgResult handleBizException(HttpServletRequest request, BizException e) {String contextMessage = buildContextMessage(request);
log.warn("BizException:code:{}, message:{}, contextMessage:{}", e.getCode(), e.getMessage(), contextMessage, e);
return TgResult.fail(e.getCode(), e.getMessage());
}// 参数校验异常 ===========================================
@ExceptionHandler(BindException.class)
public TgResult handleBindException(HttpServletRequest request, BindException e) {StringBuilder sb = new StringBuilder();
e.getBindingResult().getAllErrors().forEach(error -> {sb.append(error.getDefaultMessage()).append("\r\n");
});
String contextMessage = buildContextMessage(request);
log.warn("BindException: message:{}, contextMessage:{}", sb, contextMessage, e);
return TgResult.fail("400", sb.toString());
}@ExceptionHandler(MethodArgumentNotValidException.class)
public TgResult handleMethodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException e) {StringBuilder sb = new StringBuilder();
e.getBindingResult().getAllErrors().forEach(error -> {sb.append(error.getDefaultMessage()).append("\r\n");
});
String contextMessage = buildContextMessage(request);
log.warn("MethodArgumentNotValidException: message:{}, contextMessage:{}", sb, contextMessage, e);
return TgResult.fail("400", sb.toString());
}// 通用异常兜底 ===========================================
@ExceptionHandler(Exception.class)
public TgResult handleException(HttpServletRequest request, Exception e) {String contextMessage = buildContextMessage(request);
log.warn("Exception: message:{}, contextMessage:{}", e.getMessage(), contextMessage, e);
return TgResult.fail("500", "服务器内部错误");
}

最后

看到这,觉得有帮助的,刷波666,投个票,感谢大家的支持~

想要看更多实战好文章,还是给大家推荐我的实战专栏–>《基于SpringBoot+SpringCloud+Vue前后端分离项目实战》,由我和 前端狗哥 合力打造的一款专栏,可以让你从0到1快速拥有企业级规范的项目实战经验!

具体的优势、规划、技术选型都可以在《开篇》试读!

订阅专栏后可以添加我的微信,我会为每一位用户进行针对性指导!

另外,别忘了关注我:天罡gg ,怕你找不到我,发布新文不容易错过: https://blog.csdn.net/scm_2008

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

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

相关文章

阿里云将关停代销业务

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 阿里云自从逐渐分拆独立之后&#xff0c;做了很多调整。最近它又做了一个大动作&#xff1a;据DoNews消息&#xff0c;阿里云将会在今年9月30日之前&#xff0c;全面关停代销业务。 这件事实际上…

企业网络日志安全与 EventLog Analyzer

企业的网络日志安全是一项至关重要的任务。随着信息技术的迅猛发展&#xff0c;网络攻击和数据泄露的威胁也与日俱增。为了应对这些威胁&#xff0c;企业需要强大的工具来监控、分析和保护其网络日志。而ManageEngine的EventLog Analyzer正是这样一款卓越的解决方案。 网络日志…

Mr. Cappuccino的第64杯咖啡——Spring循环依赖问题

Spring循环依赖问题 什么是循环依赖问题示例项目结构项目代码运行结果 Async注解导致的问题使用Lazy注解解决Async注解导致的问题开启Aop使用代理对象示例项目结构项目代码运行结果 Spring是如何解决循环依赖问题的原理源码解读 什么情况下Spring无法解决循环依赖问题 什么是循…

UbuntuDDE 23.04发布,体验DeepinV23的一个新选择

UbuntuDDE 23.04发布&#xff0c;体验DeepinV23的一个新选择 昨晚网上搜索了一圈&#xff0c;无意看到邮箱一条新闻&#xff0c;UbuntuDDE 23.04发布了 因为前几天刚用虚拟机安装过&#xff0c;所以麻溜的从网站下载了ISO文件&#xff0c;安装上看看。本来没多想&#xff0c;…

设计模式--单例模式(Singleton Pattern)

一、什么是单例模式 单例模式是一种创建型设计模式&#xff0c;它旨在确保一个类只有一个实例&#xff0c;并提供一个全局访问点来访问该实例。换句话说&#xff0c;单例模式限制了类的实例化次数为一个&#xff0c;并提供一种在应用程序中共享一个实例的方式。这对于需要只有…

2023年高教社杯 国赛数学建模思路 - 案例:FPTree-频繁模式树算法

文章目录 算法介绍FP树表示法构建FP树实现代码 建模资料 ## 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 算法介绍 FP-Tree算法全称是FrequentPattern Tree算法&#xff0c;就是频繁模式树算法&#xff0c…

FxFactory 8 Pro Mac 苹果电脑版 fcpx/ae/motion视觉特效软件包

FxFactory pro for mac是应用在Mac上的fcpx/ae/pr视觉特效插件包&#xff0c;包含了成百上千的视觉效果&#xff0c;打包了很多插件&#xff0c;如调色插件&#xff0c;转场插件&#xff0c;视觉插件&#xff0c;特效插件&#xff0c;文字插件&#xff0c;音频插件&#xff0c;…

FPGA原理与结构——FIFO IP核原理学习

一、FIFO概述 1、FIFO的定义 FIFO是英文First-In-First-Out的缩写&#xff0c;是一种先入先出的数据缓冲器&#xff0c;与一般的存储器的区别在于没有地址线&#xff0c; 使用起来简单&#xff0c;缺点是只能顺序读写数据&#xff0c;其数据地址由内部读写指针自动加1完成&…

URL中传递JSON字符串

今天遇见了一个需求&#xff0c;从post请求中在url里传递json字符串&#xff0c; 就是路径?参数11那种情况 最后怎么解决的呢&#xff1f; 需要使用前端方法&#xff0c;先用JSON.stringify格式化成字符串&#xff0c;再用encodeURIComponent把JSON里面的符号转转为url支持的…

Postman测WebSocket接口

01、WebSocket 简介 WebSocket是一种在单个TCP连接上进行全双工通信的协议。 WebSocket使得客户端和服务器之间的数据交换变得更加简单&#xff0c;允许服务端主动向客户端推送数据。在WebSocket API中&#xff0c;浏览器和服务器只需要完成一次握手&#xff0c;两者之间就直…

登录校验-JWT令牌-登陆后下发令牌

目录 思路 接口文档 令牌生成和下发 步骤 具体代码如下 工具类 控制类 测试 前后端联调 思路 令牌生成&#xff1a;登陆成功后&#xff0c;生成JWT令牌&#xff0c;并返回给前端令牌校验&#xff1a;在请求到达服务端后&#xff0c;对令牌进行统一拦截、校验 接口文档…

Powered by Paraverse | 平行云助力彼真科技打造演出“新物种”

01 怎么看待虚拟演出 彼真科技 我们怎么看待虚拟演出&#xff1f; 虚拟演出给音乐人或者音乐行业带来了哪些新的机会&#xff1f;通过呈现一场高标准的虚拟演出&#xff0c;我们的能力延伸点在哪里&#xff1f; 先说一下我们认知里的虚拟演出的本质&#xff1a; 音乐演出是一…

java 里面 long 转换int内存分析

了解补码知识点 要将补码转换为十进制&#xff0c;需要确定补码的符号位。如果补码的符号位为1&#xff0c;则表示为负数&#xff0c;否则表示为正数。 假设我们有一个补码为1 0110 1011 1100 1101 1000 0011 1101 1100 0010 1101 1111 1101 1100 0001 1100 0011 0100 首先&a…

大数据时代的软件开发实践:利用云计算和AI赋能创新

文章目录 云计算的赋能弹性资源管理远程协作与分布式开发持续集成和持续交付成本效益 人工智能的赋能数据驱动的决策自动化智能预测和优化自适应系统 创新的实践方法数据驱动的创新智能化产品开放式创新迭代和反馈 &#x1f388;个人主页&#xff1a;程序员 小侯 &#x1f390;…

C#,《小白学程序》第三课:类、类数组与排序

1 文本格式 /// <summary> /// 同学信息类 /// </summary> public class Classmate { /// <summary> /// 学号 /// </summary> public int Id; /// <summary> /// 姓名 /// </summary> public string Nam…

VR智慧课堂 | 临床兽医学VR实验教学有哪些好处?

随着科技的不断发展&#xff0c;虚拟现实(VR)技术已经逐渐渗透到各个领域&#xff0c;为人们带来了前所未有的体验。在动物医学实验教学中&#xff0c;VR技术的应用也日益受到关注。本文将探讨临床兽医学VR实验教学的好处。 首先&#xff0c;VR技术能够提高动物医学实验的安全性…

【25考研】- 整体规划及高数一起步

【25考研】- 整体规划及高数一起步 一、整体规划二、专业课870计算机应用基础参考网上考研学长学姐&#xff1a; 三、高数一典型题目、易错点及常用结论&#xff08;一&#xff09;典型题目&#xff08;二&#xff09;易错点&#xff08;三&#xff09;常用结论1.令tarctanx, 则…

抖音seo矩阵系统源代码开发部署分享

一、 开发步骤分享 抖音SEO矩阵系统源代码开发部署分享&#xff0c;需要经验丰富的开发人员和服务器管理人员&#xff0c;以下是大致的步骤&#xff1a; 确定你需要的功能和设计&#xff0c;确定开发人员和设计师的角色和任务分配&#xff0c;以及开发进度和计划。 确定服务器…

【趣味随笔】怎么维护自己的电脑?

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

node.js安装好后测试报错解决

node.js的版本是18.X.X node.js安装好后&#xff0c;执行命令&#xff1a; npm install express -g 报错&#xff01;&#xff01;&#xff01; 解决办法&#xff1a; 看报错是由于权限不够&#xff0c; 所以打开cmd时&#xff0c;以管理员的方式打开 然后再执行命令就OK了…