技术派全局异常处理

前言

全局的异常处理是Java后端不可或缺的一部分,可以提高代码的健壮性和可维护性。

在我们的开发中,总是难免会碰到一些未经处理的异常,假如没有做全局异常处理,那么我们返回给用户的信息应该是不友好的,很抽象的,用户会认为我们的程序是不安全的。

相反,如果有了全局异常处理,那么我们就可以给用户提供更友好的反馈。

我们甚至可以把全局异常处理写到简历上,比如说你可以这样描述:项目采用了 HandlerExceptionResolver(或者 ControllerAdvice 方案)的全局异常处理策略,提高了代码的健壮性和可维护性,优化了用户体验。

我会结合具体的业务场景给大家一种身临其境的感觉(@),讲一讲HandlerExceptionResolver 和 ControllerAdvice 具体怎么在项目中使用。

业务场景

技术派整合了Redis,比如用户登录的时候会从Redis中获取缓存,那假如我们没有启动Redis服务呢?

然后我们在本地启动技术派的服务端。

然后点击登录 ->一键登录。

然后就会收到这样一条提示信息。

由于我们项目是开源的,所以这里就直接把服务端的信息返回出来,好让大家第一时间辨别出是哪里出了问题,可以及时去调整。

当你看到这样一条错误提示,第一时间就能明白,哦,原来是 Redis 没有启动啊。

在服务器端的控制台面板中(错误堆栈信息中),可以找到对应的错误信恙。

其中 ForumExceptionHandler 就是用来进行全局异常处理的,它是HandlerExceptionResolver 接囗的实现类

HandlerExceptionResolver

HandlerExceptionResolver 是 Spring 提供的一种异常处理机制,它允许我们在应用程序中以统一的方式处理控制器方法引发的异常。

要使用 HandlerExceptionResolver,我们需要创建一个实现该接口的类,并在其中定义如何处理异常。例如:

@Slf4j
@Order(-100)
public class ForumExceptionHandler implements HandlerExceptionResolver {@Overridepublic ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {}

  • @Slf4j 是 lombok 提供的一个日志注解。
  • @0rder 注解用于指定 Spring 中组件的加载顺序。它接受一个整数值,数值越小,组件的优先级越高,加载顺序越靠前。
  • 在 resolveException 方法中,我们可以自定义异常处理逻辑,根据异常类型返回不同的 ModelAndView。

我们来看一下 resolveException 方法中的具体写法:

@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {Status errStatus = buildToastMsg(ex);if (restResponse(request, response)) {// 表示返回json数据格式的异常提示信息if (response.isCommitted()) {// 如果返回已经提交过,直接退出即可return new ModelAndView();}try {response.reset();// 若是rest接口请求异常时,返回json格式的异常数据;而不是专门的500页面response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);response.setHeader("Cache-Control", "no-cache, must-revalidate");response.getWriter().println(JsonUtil.toStr(ResVo.fail(errStatus)));response.getWriter().flush();response.getWriter().close();return new ModelAndView();} catch (Exception e) {throw new RuntimeException(e);}}String view = getErrorPage(errStatus, response);ModelAndView mv = new ModelAndView(view);response.setContentType(MediaType.TEXT_HTML_VALUE);mv.getModel().put("global", SpringUtil.getBean(GlobalInitService.class).globalAttr());mv.getModel().put("res", ResVo.fail(errStatus));mv.getModel().put("toast", JsonUtil.toStr(ResVo.fail(errStatus)));return mv;
}

技术派做了两种处理,一种是REST接口请求的异常,一种是针对普通页请求的异常。

1、如果是 REST 接口请求异常,代码会返回一个 JSON 格式的异常提示信息:

  • 首先检查响应是否已经提交,如果已经提交,则直接返回一个空的 ModelAndView。
  • 如果响应未提交,将重置响应对象,设置响应的内容类型为JSON,并添加相关的响应头。
  • 使用 response.getwriter()将异常状态对象 errStatus 转换为 JSON 格式并写入响应。完成后,返回一个空的 ModelAndView。

②、如果是普通页面请求异常,代码会返回一个包含错误信息的 HTML 页面:

  • 根据异常状态对象 errStatus 和响应对象 response 获取错误页面的视图名称。
  • 创建-个 ModelAndView 对象,并设置视图名称。
  • 设置响应的内容类型为 HTML。
  • 向 ModelAndView 中添加全局属性、错误响应对象以及错误信息(以JSON 格式)
  • 最后返回这个 ModelAndView 对象,用于展示错误页面。

下图是当遇到 404 错误的时候,返回的 404 页面。

其中 buildToastMsg 方法用来对异常进行分类,使用 instanceof 关键字来判断不同类型的异常,添加不同的异常码和提示消息。

private Status buildToastMsg(Exception ex) {
if (ex instanceof ForumException) {return ((ForumException) ex).getStatus();
} else if (ex instanceof AsyncRequestTimeoutException) {return Status.newStatus(StatusEnum.UNEXPECT_ERROR, "超时未登录");
} else if (ex instanceof HttpMediaTypeNotAcceptableException) {return Status.newStatus(StatusEnum.RECORDS_NOT_EXISTS, ExceptionUtils.getStackTrace(ex));
} else if (ex instanceof HttpRequestMethodNotSupportedException || ex instanceof MethodArgumentTypeMismatchException || ex instanceof IOException) {// 请求方法不匹配return Status.newStatus(StatusEnum.ILLEGAL_ARGUMENTS, ExceptionUtils.getStackTrace(ex));
} else if (ex instanceof NestedRuntimeException) {log.error("unexpect NestedRuntimeException error! {}", ReqInfoContext.getReqInfo(), ex);return Status.newStatus(StatusEnum.UNEXPECT_ERROR, ex.getMessage());
} else {log.error("unexpect error! {}", ReqInfoContext.getReqInfo(), ex);return Status.newStatus(StatusEnum.UNEXPECT_ERROR, ExceptionUtils.getStackTrace(ex));
}
}

StatusEnum 中定义了异常码的规范,举几个例子。

/*** 异常码规范:* xxx - xxx - xxx* 业务 - 状态 - code* <p>* 业务取值* - 100 全局* - 200 文章相关* - 300 评论相关* - 400 用户相关* <p>* 状态:基于http status的含义* - 4xx 调用方使用姿势问题* - 5xx 服务内部问题* <p>* code: 具体的业务code*/
@Getter
public enum StatusEnum {SUCCESS(0, "OK"),// -------------------------------- 通用// 全局传参异常ILLEGAL_ARGUMENTS(100_400_001, "参数异常"),ILLEGAL_ARGUMENTS_MIXED(100_400_002, "参数异常:%s"),// 全局权限相关FORBID_ERROR(100_403_001, "无权限"),
}

getErrorPage 方法用于返回不同的错误页面,比如常见的 404 Not Found(请求的资源不存在,服务器无法找到请求的资源)、403 Forbidden(服务器理解请求,但是拒绝处理它,一般是由于权限问题或者访问被拒绝)500 lnternal Server Error(服务器发生了错误,无法完成请求)等。

private String getErrorPage(Status status, HttpServletResponse response) {// 根据异常码解析需要返回的错误页面if (StatusEnum.is5xx(status.getCode())) {response.setStatus(500);return "error/500";} else if (StatusEnum.is403(status.getCode())) {response.setStatus(403);return "error/403";} else {response.setStatus(404);return "error/404";}
}

restResponse 方法用来判断是否是 REST 请求,比如说 admin 后台请求、api数据请求、上传图片等接口

Ajax 请求等,这些请求统一返回 JSON 格式的异常提示信息,否则返回普通的页面格式的异常提示信息。

**
* 后台请求、api数据请求、上传图片等接口,返回json格式的异常提示信息
* 其他异常,返回500的页面
*
* @param request
* @param response
* @return
*/
private boolean restResponse(HttpServletRequest request, HttpServletResponse response) {if (request.getRequestURI().startsWith("/api/admin/") || request.getRequestURI().startsWith("/admin/")) {return true;}if (request.getRequestURI().startsWith("/image/upload")) {return true;}if (response.getContentType() != null && response.getContentType().contains(MediaType.APPLICATION_JSON_VALUE)) {return true;}if (isAjaxRequest(request)) {return true;}// 数据接口请求AntPathMatcher pathMatcher = new AntPathMatcher();if (pathMatcher.match("/**/api/**", request.getRequestURI())) {return true;}return false;
}

再来看一下自定义的异常类 ForumException,非常简单,继承了 RuntimeException。

/*** 业务异常**/
public class ForumException extends RuntimeException {@Getterprivate Status status;public ForumException(Status status) {this.status = status;}public ForumException(int code, String msg) {this.status = Status.newStatus(code, msg);}public ForumException(StatusEnum statusEnum, Object... args) {this.status = Status.newStatus(statusEnum, args);}}

HandlerExceptionResolver 的工作原理主要基于 Spring MVC 的异常处理流程。当一个请求进入 Spring MVC后,它会根据请求信息找到对应的处理器(handler,也就是Controller)。在Controller 执行过程中,如果抛出了异常,Spring MVC 就会启动异常处理流程。

1)异常发生:当 Controller 执行过程中抛出异常,Spring MVC 捕获到这个异常后,会进入异常处理流程

2)查找异常解析器:Spring MVC 会遍历所有已注册的 HandlerExceptionResolver 实现。比如说我们自定义的

ForumExceptionHandler,Spring MVC 本身也提供了一些默认的实现,比如DefaultHandlerExceptionResolver、ExceptionHandlerExceptionResolver.

3)执行异常解析器:对于每个 HandlerExceptionResolver 实现,Spring MVC会调用它的 resolveException方法,并传入请求、响应、处理器和异常对象。如果解析器能处理这个异常,它会返回一个非空的ModelAndView 对象。这个对象封装了异常处理后的视图和模型数据。

4)处理返回结果:当 resolveException 方法返回一个非空的 ModelAndView 对象时,Spring MVC 会将这个对象用于生成最终的响应。可能渲染一个错误视图、设置响应状态码等。如果所有的HandlerExceptionResolver 都无法处理这个异常(即都返回了空的 ModelAndView对象),那么 SpringMVC 会将异常重新抛出,以便其他异常处理器(如 Servet 容器)进行处理。

通过这个流程,HandlerExceptionResolver 能够在 Spring MVC 中统一管理和处理异常。记得在 Spring Boot的启动类中将自定义的 HandlerExceptionResolver 添加到 Spring 配置中。

@Slf4j
@EnableAsync
@EnableScheduling
@EnableCaching
@ServletComponentScan  //与@WebFilter(urlPatterns = "/*", filterName = "reqRecordFilter", asyncSupported = true)注解配套
@SpringBootApplication
public class QuickForumApplication implements WebMvcConfigurer, ApplicationRunner {@Overridepublic void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {resolvers.add(0, new ForumExceptionHandler());}
}

@ControllerAdvice

除了 HandlerExceptionResolver,全局异常还可以采用 @ControllerAdvice 注解的方式。它可以将通用的操作和逻辑抽离出来,避免在每个控制器中重复相同的操作。

第一步,新建一个自定义的异常类 ForumAdviceException。

/*** 业务异常**/
public class ForumAdviceException extends RuntimeException {@Getterprivate Status status;public ForumAdviceException(Status status) {this.status = status;}public ForumAdviceException(int code, String msg) {this.status = Status.newStatus(code, msg);}public ForumAdviceException(StatusEnum statusEnum, Object... args) {this.status = Status.newStatus(statusEnum, args);}}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Status {/*** 业务状态码*/@ApiModelProperty(value = "状态码, 0表示成功返回,其他异常返回", required = true, example = "0")private int code;/*** 描述信息*/@ApiModelProperty(value = "正确返回时为ok,异常时为描述文案", required = true, example = "ok")private String msg;public static Status newStatus(int code, String msg) {return new Status(code, msg);}public static Status newStatus(StatusEnum status, Object... msgs) {String msg;if (msgs.length > 0) {msg = String.format(status.getMsg(), msgs);} else {msg = status.getMsg();}return newStatus(status.getCode(), msg);}
}

第二步,新建一个全局异常控制器 GlobalExceptionHandler,内容如下所示。

@RestControllerAdvice
public class GlobalExceptionHandler {@ExceptionHandler(value = ForumAdviceException.class)public ResVo<String> handleForumAdviceException(ForumAdviceException e) {return ResVo.fail(e.getStatus());}
}

@RestControllerAdvice 是一个特殊的 @ControllerAdvice 注解,适用于处理 RESTfu API 异常的情况。这意味着它将用于处理来自带有 @RestController 注解的控制器抛出的异常。

此类中定义的方法 handleForumAdviceException 使用 @ExceptionHandler 注解,表示它将处理ForumAdviceException 类型的异常。

第三步,加一个测试的控制器方法 testControllerAdvice。

@RequestMapping(path = "testControllerAdvice")
@ResponseBody
public String testControllerAdvice() {throw new ForumAdviceException(StatusEnum.ILLEGAL_ARGUMENTS_MIXED, "测试ControllerAdvice异常");
}

第四步,如果之前在启动类中注册了 ForumExceptionHandler,此时需要干掉。

第五步,重启启动服务,测试 testControllerAdvice 接口。

接口返回的内容如下所示

两种全局异常处理的优缺点

好,我们来对比一下两种全局异常处理 HandlerExceptionResolver 和 @ControllerAdvice(或@RestControllerAdvice)的优缺点。

1、HandlerExceptionResolver

HandlerExceptionResolver 是一个接口,用于处理由 Controller 抛出的异常,我们可以重写resolveException方法,在其中实现该接口来自定义全局异常处理逻辑。然后在Spring Boot 的启动类中通过

extendHandlerExceptionResolvers 将自定义的 HandlerExceptionResolver 添加到解析器中。

  • 优点:可以更加灵活地处理异常,因为你可以编写任何处理逻辑。
  • 缺点:与其他 Spring MVC 组件的集成不够紧密,需要手动添加和配置,。

2、@ControllerAdvice(或@RestControllerAdvice)

@ControllerAdvice(或 @RestControllerAdvice)是用于定义全局异常处理类的注解。在这个类中,我们可以使用 @ExceptionHandler 注解来处理不同类型的异常。@ControllerAdvice 需要与 @ExceptionHandler 注解一起使用。

  • 优点:更容易实现和集成,只需创建一个带有 @ControllerAdvice(或 @RestControllerAdvice)注解的类,并使用 @ExceptionHandler 注解定义异常处理方法。
  • 缺点:异常处理逻辑可能不如 HandlerxceptionResolver 那么灵活。

我们可以根据具体的需求和使用场景,选择其中之一来实现全局异常处理。另外二者可以共存,

ControllerAddvice优先级比HandlerExceptionResolver高,也可以用@order注解指定优先级。

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

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

相关文章

【一篇文章带你搞懂--拉链表!!!拉链表的原理是什么!】

前言&#xff1a; &#x1f49e;&#x1f49e;大家好&#xff0c;我是书生♡&#xff0c;今天主要和大家分享一下拉链表的原理以及使用,希望对大家有所帮助。 大家可以关注我下方的链接更多优质文章供学习参考。 &#x1f49e;&#x1f49e;代码是你的画笔&#xff0c;创新是你…

Flink 运行时架构

Flink 运行时的组件 作业管理器&#xff08;JobManager&#xff09;资源管理器&#xff08;ResourceManager&#xff09;任务管理器&#xff08;TaskManager&#xff09;分发器&#xff08;Dispatch&#xff09; JobManager 控制一个应用程序执行的主进程&#xff0c;也就是说…

LiveNVR监控流媒体Onvif/RTSP用户手册-概览:CPU使用、存储使用、带宽使用、负载、内存使用、通道统计

LiveNVR监控流媒体Onvif/RTSP用户手册-概览:CPU使用、存储使用、带宽使用、负载、内存使用、通道统计 1、概览1.1、通道统计1.2、负载1.3、CPU使用1.4、存储使用1.5、带宽使用1.6、内存使用 2、RTSP/HLS/FLV/RTMP拉流Onvif流媒体服务 1、概览 1.1、通道统计 显示可用通道&…

Lipschitz 连续,绝对连续

1. Lipschitz 连续 经常听到这个名词&#xff0c; Lipschitz 连续比普通连续更强&#xff0c;不仅要求函数连续&#xff0c;还要求函数的梯度小于一个正实数。 在单变量实数函数上的定义可以是&#xff1a; 对于定义域内任意两个 x 1 x_1 x1​ and x 2 x_2 x2​, 存在一个…

云计算与生成式AI的技术盛宴!亚马逊云科技深圳 Community Day 社区活动流程抢先知道!

小李哥最近要给大家分享7月7日在深圳的即将举办的亚马逊云科技生成式AI社区活动Community Day &#xff0c;干货很多内容非常硬核&#xff0c;不仅有技术分享学习前沿AI技术&#xff0c;大家在现场还可以动手实践沉浸式体验大模型&#xff0c;另外参与现场活动还可以领取诸多精…

顺序表(C语言详细版)

1. 线性表 线性表(lina list)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列、字符串...... 线性表在逻辑上是线性结构&#xff0c;也就是说连续的一条直线。但是在物理结构上并…

一文汇总全球热门新闻API

新闻API通过提供快速、准确和全面的新闻内容&#xff0c;已经成为现代社会不可或缺的一部分&#xff0c;对人们的生活、工作环境和科技发展产生了深远的影响。新闻API使人们能够快速获取来自世界各地的实时新闻和信息&#xff0c;提高了信息的可访问性。通过新闻API&#xff0c…

C++算法学习心得八.动态规划算法(6)

1.最长递增子序列&#xff08;300题&#xff09; 题目描述&#xff1a; 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&…

Redis分布式集群部署

目录 一. 原理简述 二. 集群配置​​​​​​​ 2.1 环境准备 2.2 编译安装一个redis 2.3 创建集群 2.4 写入数据测试 实验一&#xff1a; 实验二&#xff1a; 实验三&#xff1a; 实验四&#xff1a; 添加节点 自动分配槽位 提升节点为master&#xff1a; 实验…

关于电商平台分类||电商平台商品分类接口|电商平台商品数据

电商平台 做电商&#xff0c;则要有电商平台&#xff0c;一个为 企业 或 个人 提供网上交易洽谈的平台。. 企业电子商务平台是建立在 Internet 网上进行商务活动的虚拟网络空间和保障商务顺利运营的管理环境&#xff1b;是协调、整合 信息流 、货物流、 资金流 有序、关联、高效…

会员信息一键同步!微盟与客如云联手打造智能服务新体验!

客户介绍 某房地产开发有限公司&#xff0c;自成立以来一直深耕于房地产行业&#xff0c;凭借卓越的开发实力和前瞻性的市场眼光&#xff0c;成为了业界备受瞩目的企业。多年来&#xff0c;该公司始终坚持“品质至上&#xff0c;客户为先”的经营理念&#xff0c;致力于为客户…

掌握React与TypeScript:从零开始绘制中国地图

最近我需要使用reactts绘制一个界面&#xff0c;里面需要以中国地图的形式展示区块链从2019-2024年这五年的备案以及注销情况&#xff0c;所以研究了一下这方面的工作&#xff0c;初步有了一些成果&#xff0c;所以现在做一些分享&#xff0c;希望对大家有帮助&#xff01; 在这…

手把手搞定报名亚马逊科技认证

引言 亚马逊云科技认证考试为我们这些技术从业者提供了提升专业技能的机会。无论选择线上还是线下考试&#xff0c;每种方式都有其独特的优势和挑战。选择合适的考试方式将帮助我们更好地展示自己的技术水平。以下是我对不同考试方式的优缺点介绍&#xff0c;以及各科目的考试…

【pytorch12】什么是梯度

说明 导数偏微分梯度 梯度&#xff1a;是一个向量&#xff0c;向量的每一个轴是每一个方向上的偏微分 梯度是有方向也有大小&#xff0c;梯度的方向代表函数在当前点的一个增长的方向&#xff0c;然后这个向量的长度代表了这个点增长的速率 蓝色代表比较小的值&#xff0c;红色…

七月论文审稿GPT第5版:拿我司七月的早期paper-7方面review数据集微调LLama 3

前言 llama 3出来后&#xff0c;为了通过paper-review的数据集微调3&#xff0c;有以下各种方式 不用任何框架 工具 技术&#xff0c;直接微调原生的llama 3&#xff0c;毕竟也有8k长度了 效果不期望有多高&#xff0c;纯作为baseline通过PI&#xff0c;把llama 3的8K长度扩展…

MySQL-行级锁(行锁、间隙锁、临键锁)

文章目录 1、介绍2、查看意向锁及行锁的加锁情况3、行锁的演示3.1、普通的select语句&#xff0c;执行时&#xff0c;不会加锁3.2、select * from stu where id 1 lock in share mode;3.3、共享锁与共享锁之间兼容。3.4、共享锁与排他锁之间互斥。3.5、排它锁与排他锁之间互斥3…

每日一题---OJ题:分隔链表

片头 嗨&#xff01;小伙伴们&#xff0c;大家好&#xff01;今天我们一起来看看这道题----分隔链表 emmmm&#xff0c;这道题&#xff0c;看描述应该不算太难&#xff0c;我们一起来画一画图呗&#xff01; 题目读懂了&#xff0c;那么如何破解这道题呢&#xff1f; 思路&…

短视频世界对我温柔以待:成都柏煜文化传媒有限公司

短视频世界对我温柔以待 在繁忙的都市生活中&#xff0c;每个人都在为生活奔波&#xff0c;为梦想努力。而在这个快节奏的时代里&#xff0c;短视频如同一股清流&#xff0c;以其独特的魅力&#xff0c;为我带来了片刻的宁静与温柔。它像是一个无声的朋友&#xff0c;在我疲惫…

(必看图文)Hadoop集群安装及MapReduce应用(手把手详解版)

前言 随着大数据时代的到来&#xff0c;处理和分析海量数据已成为企业和科研机构不可或缺的能力。Hadoop&#xff0c;作为开源的分布式计算平台&#xff0c;因其强大的数据处理能力和良好的可扩展性&#xff0c;成为大数据处理领域的佼佼者。本图文教程旨在帮助读者理解Hadoop集…

Mysql面试合集

概念 是一个开源的关系型数据库。 数据库事务及其特性 事务&#xff1a;是一系列的数据库操作&#xff0c;是数据库应用的基本逻辑单位。 事务特性&#xff1a; &#xff08;1&#xff09;原子性&#xff1a;即不可分割性&#xff0c;事务要么全部被执行&#xff0c;要么就…