SpringBoot 如何进行全局异常处理

前 言

在SpringBoot的开发中,为了提高程序运行的鲁棒性,我们经常需要对各种程序异常进行处理,但是如果在每个出异常的地方进行单独处理的话,这会引入大量业务不相关的异常处理代码,增加了程序的耦合,同时未来想改变异常的处理逻辑,也变得比较困难。这篇文章带大家了解一下如何优雅的进行全局异常处理。
为了实现全局拦截,这里使用到了Spring中提供的两个注解,@RestControllerAdvice和@ExceptionHandler,结合使用可以拦截程序中产生的异常,并且根据不同的异常类型分别处理。下面我会先介绍如何利用这两个注解,优雅的完成全局异常的处理,接着解释这背后的原理。

1. 如何实现全局拦截?

1.1 自定义异常处理类

在下面的例子中,我们继承了ResponseEntityExceptionHandler并使用@RestControllerAdvice注解了这个类,接着结合@ExceptionHandler针对不同的异常类型,来定义不同的异常处理方法。这里可以看到我处理的异常是自定义异常,后续我会展开介绍。
ResponseEntityExceptionHandler中包装了各种SpringMVC在处理请求时可能抛出的异常的处理,处理结果都是封装成一个ResponseEntity对象。ResponseEntityExceptionHandler是一个抽象类,通常我们需要定义一个用来处理异常的使用@RestControllerAdvice注解标注的异常处理类来继承自ResponseEntityExceptionHandler。ResponseEntityExceptionHandler中为每个异常的处理都单独定义了一个方法,如果默认的处理不能满足你的需求,则可以重写对某个异常的处理。

@Log4j2  
@RestControllerAdvice  
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {  /**  * 定义要捕获的异常 可以多个 @ExceptionHandler({})     *  * @param request  request  * @param e        exception  * @param response response  * @return 响应结果  */  @ExceptionHandler(AuroraRuntimeException.class)  public GenericResponse customExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {  AuroraRuntimeException exception = (AuroraRuntimeException) e;  if (exception.getCode() == ResponseCode.USER_INPUT_ERROR) {  response.setStatus(HttpStatus.BAD_REQUEST.value());  } else if (exception.getCode() == ResponseCode.FORBIDDEN) {  response.setStatus(HttpStatus.FORBIDDEN.value());  } else {  response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());  }  return new GenericResponse(exception.getCode(), null, exception.getMessage());  }  @ExceptionHandler(NotLoginException.class)  public GenericResponse tokenExceptionHandler(HttpServletRequest request, final Exception e, HttpServletResponse response) {  log.error("token exception", e);  response.setStatus(HttpStatus.FORBIDDEN.value());  return new GenericResponse(ResponseCode.AUTHENTICATION_NEEDED);  }  }

1.2 定义异常码

这里定义了常见的几种异常码,主要用在抛出自定义异常时,对不同的情形进行区分。

@Getter  
public enum ResponseCode {  SUCCESS(0, "Success"),  INTERNAL_ERROR(1, "服务器内部错误"),  USER_INPUT_ERROR(2, "用户输入错误"),  AUTHENTICATION_NEEDED(3, "Token过期或无效"),  FORBIDDEN(4, "禁止访问"),  TOO_FREQUENT_VISIT(5, "访问太频繁,请休息一会儿");  private final int code;  private final String message;  private final Response.Status status;  ResponseCode(int code, String message, Response.Status status) {  this.code = code;  this.message = message;  this.status = status;  }  ResponseCode(int code, String message) {  this(code, message, Response.Status.INTERNAL_SERVER_ERROR);  }  }

1.3 自定义异常类

这里我定义了一个AuroraRuntimeException的异常,就是在上面的异常处理函数中,用到的异常。每个异常实例会有一个对应的异常码,也就是前面刚定义好的。

@Getter  
public class AuroraRuntimeException extends RuntimeException {  private final ResponseCode code;  public AuroraRuntimeException() {  super(String.format("%s", ResponseCode.INTERNAL_ERROR.getMessage()));  this.code = ResponseCode.INTERNAL_ERROR;  }  public AuroraRuntimeException(Throwable e) {  super(e);  this.code = ResponseCode.INTERNAL_ERROR;  }  public AuroraRuntimeException(String msg) {  this(ResponseCode.INTERNAL_ERROR, msg);  }  public AuroraRuntimeException(ResponseCode code) {  super(String.format("%s", code.getMessage()));  this.code = code;  }  public AuroraRuntimeException(ResponseCode code, String msg) {  super(msg);  this.code = code;  }  }

1.4 自定义返回类型

为了保证各个接口的返回统一,这里专门定义了一个返回类型。

@Getter  
@Setter  
public class GenericResponse<T> {  private int code;  private T data;  private String message;  public GenericResponse() {};  public GenericResponse(int code, T data) {  this.code = code;  this.data = data;  }  public GenericResponse(int code, T data, String message) {  this(code, data);  this.message = message;  }  public GenericResponse(ResponseCode responseCode) {  this.code = responseCode.getCode();  this.data = null;  this.message = responseCode.getMessage();  }  public GenericResponse(ResponseCode responseCode, T data) {  this(responseCode);  this.data = data;  }  public GenericResponse(ResponseCode responseCode, T data, String message) {  this(responseCode, data);  this.message = message;  }  
}

实际测试异常

下面的例子中,我们想获取到用户的信息,如果用户的信息不存在,可以直接抛出一个异常,这个异常会被我们上面定义的全局异常处理方法所捕获,然后根据不同的异常编码,完成不同的处理和返回。

public User getUserInfo(Long userId) {  // some logicUser user = daoFactory.getExtendedUserMapper().selectByPrimaryKey(userId);  if (user == null) {  throw new AuroraRuntimeException(ResponseCode.USER_INPUT_ERROR, "用户id不存在");  }// some logic....
}

以上就完成了整个全局异常的处理过程,接下来重点说说为什么@RestControllerAdvice和@ExceptionHandler结合使用可以拦截程序中产生的异常?

全局拦截的背后原理?

下面会提到@ControllerAdvice注解,简单地说,@RestControllerAdvice与@ControllerAdvice的区别就和@RestController与@Controller的区别类似,@RestControllerAdvice注解包含了@ControllerAdvice注解和@ResponseBody注解。

public class DispatcherServlet extends FrameworkServlet {// ......protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);// 重点关注initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}// ......
}

在initHandlerExceptionResolvers(context)方法中,会取得所有实现了HandlerExceptionResolver接口的bean并保存起来,其中就有一个类型为ExceptionHandlerExceptionResolver的bean,这个bean在应用启动过程中会获取所有被@ControllerAdvice注解标注的bean对象做进一步处理,关键代码在这里:

public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolverimplements ApplicationContextAware, InitializingBean {// ......private void initExceptionHandlerAdviceCache() {// ......List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());AnnotationAwareOrderComparator.sort(adviceBeans);for (ControllerAdviceBean adviceBean : adviceBeans) {ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(adviceBean.getBeanType());if (resolver.hasExceptionMappings()) {// 找到所有ExceptionHandler标注的方法并保存成一个ExceptionHandlerMethodResolver类型的对象缓存起来this.exceptionHandlerAdviceCache.put(adviceBean, resolver);if (logger.isInfoEnabled()) {logger.info("Detected @ExceptionHandler methods in " + adviceBean);}}// ......}}// ......
}

当Controller抛出异常时,DispatcherServlet通过ExceptionHandlerExceptionResolver来解析异常,而ExceptionHandlerExceptionResolver又通过ExceptionHandlerMethodResolver 来解析异常, ExceptionHandlerMethodResolver 最终解析异常找到适用的@ExceptionHandler标注的方法是这里:

public class ExceptionHandlerMethodResolver {// ......private Method getMappedMethod(Class<? extends Throwable> exceptionType) {List<Class<? extends Throwable>> matches = new ArrayList<Class<? extends Throwable>>();// 找到所有适用于Controller抛出异常的处理方法,例如Controller抛出的异常// 是AuroraRuntimeException(继承自RuntimeException),那么@ExceptionHandler(AuroraRuntimeException.class)和// @ExceptionHandler(Exception.class)标注的方法都适用此异常for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {if (mappedException.isAssignableFrom(exceptionType)) {matches.add(mappedException);}}if (!matches.isEmpty()) {/* 这里通过排序找到最适用的方法,排序的规则依据抛出异常相对于声明异常的深度,例如Controller抛出的异常是是AuroraRuntimeException(继承自RuntimeException),那么AuroraRuntimeException相对于@ExceptionHandler(AuroraRuntimeException.class)声明的AuroraRuntimeException.class其深度是0,相对于@ExceptionHandler(Exception.class)声明的Exception.class其深度是2,所以@ExceptionHandler(BizException.class)标注的方法会排在前面 */Collections.sort(matches, new ExceptionDepthComparator(exceptionType));return this.mappedMethods.get(matches.get(0));}else {return null;}}// ......
}

整个@RestControllerAdvice处理的流程就是这样,结合@ExceptionHandler就完成了对不同异常的灵活处理。

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

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

相关文章

移动端Vant中的Calendar日历增加显示农历(节日、节气)功能

核心&#xff1a; 使用 js-calendar-converter 库实现 npm地址&#xff1a;js-calendar-converter 内部使用原生calendar.js&#xff0c; 中国农历&#xff08;阴阳历&#xff09;和西元阳历即公历互转JavaScript库&#xff0c;具体实现感兴趣的可自行查看其实现源码。 原日…

唱作歌手朱卫明的粤语版《兄弟情》:一曲深情唱尽人间真挚情感

朱卫明的粤语版《兄弟情》&#xff1a;一曲深情唱尽人间真挚情感 在音乐的广阔天地里&#xff0c;每种语言都有其独特的韵味和魅力。对于唱作音乐人朱卫明来说&#xff0c;他用普通话演唱的歌曲已经深入人心&#xff0c;但当他将《兄弟情》这首歌曲以粤语演绎时&#xff0c;更…

笔记 - 现代嵌入式芯片封装识读

0.引用&#xff1a; 配图、资料并非一处采集&#xff0c;我不太容易找到图片的原始链接。这里的图片仅作示例&#xff0c;无商业用途。如果涉及侵权&#xff0c;请随时联系。谢谢&#xff01; PCB封装欣赏了解之旅&#xff08;下篇&#xff09;—— 常用集成电路_ufqfpn封装…

【C++】STL 容器 - string 字符串操作 ⑤ ( string 字符串查找 | find 函数查找字符串 | rfind 函数查找字符串 )

文章目录 一、string 字符查找 - find 函数查找字符串1、string 类 find 函数原型说明2、代码示例 - 字符串查找3、代码示例 - 统计字符串子串 二、string 字符查找 - rfind 函数查找字符串1、string 类 rfind 函数原型说明2、代码示例 - rfind 字符串查找 一、string 字符查找…

华为OD机试真题-英文输入法-2023年OD统一考试(C卷)

题目描述: 主管期望你来实现英文输入法单词联想功能。需求如下: 依据用户输入的单词前缀,从已输入的英文语句中联想出用户想输入的单词,按字典序输出联想到的单词序列,如果联想不到,请输出用户输入的单词前缀。 注意: 1. 英文单词联想时,区分大小写 2. 缩略形式…

探索大学专业和硕士专业的广阔领域

目录 大学专业分类 1. 自然科学和数学类专业 2. 工程与技术类专业 3. 医学与卫生类专业 4. 社会科学类专业 5. 人文学科类专业 6. 艺术与设计类专业 硕士专业分类 1. 工程管理与技术管理 2. 商业与管理 3. 计算机科学与信息技术 4. 医学与卫生管理 5. 社会科学与人…

注册与回调

C 再谈谈注册(本质是建立映射)与回调 在之前的博文中&#xff0c; 我们探讨过映射的重要作用&#xff0c; 请直接看&#xff1a;http://blog.csdn.net/stpeace/article/details/39452203, 在那篇文章中&#xff0c; 我们是用STL中的map来做的&#xff0c; map建立的是key-value…

rk3568 RGMII KSZ8795 MAC TO MAC

RK3568与KSZ8795交换机芯片连接&#xff0c;直接MAC TO MAC方式&#xff0c;这样一下就扩展会4路网口&#xff0c;应该场合比较多&#xff0c;移植过程如下&#xff1a; 参考《Rockchip_Developer_Guide_Linux_MAC_TO_MAC_CN.pdf》 《rockchip RGMIImv88e6390 管理型交换机功…

【Spring】之Ioc和Aop快速了解

这里写目录标题 1.Spring框架是什么&#xff1f;简介&#xff1a;总结&#xff1a;Spring框架&#xff0c;可以理解为是一个管理者&#xff1a;管理整个分层架构&#xff08;MVC&#xff09;中的每一个对象&#xff1b;&#xff08;每一个对象称之为bean&#xff09; 2.Spring框…

太空旅行:计算机技术的崭新航程

太空旅行&#xff1a;计算机技术的崭新航程 一、引言 自古以来&#xff0c;人类就对浩渺的宇宙充满了无尽的好奇和渴望。随着科技的飞速发展&#xff0c;太空旅行已经从科幻小说中的构想变为现实。在这个过程中&#xff0c;计算机技术起到了不可或缺的作用。从阿波罗时代的初…

​FL Studio2024最新版本好不好用?有哪些新功能

FL Studio2024版是一款在国内非常受欢迎的多功能音频处理软件&#xff0c;我们可以通过这款软件来对多种不同格式的音频文件来进行编辑处理。而且FL Studio 2024版还为用户们准备了超多的音乐乐器伴奏&#xff0c;我们可以直接一键调取自己需要的音调。 FL Studio 2024版不仅拥…

HarmonyOS4.0从零开始的开发教程15HTTP数据请求

HarmonyOS&#xff08;十三&#xff09;HTTP数据请求 1 概述 日常生活中我们使用应用程序看新闻、发送消息等&#xff0c;都需要连接到互联网&#xff0c;从服务端获取数据。例如&#xff0c;新闻应用可以从新闻服务器中获取最新的热点新闻&#xff0c;从而给用户打造更加丰富…

uniapp上传文件api如何使用

在uni-app中使用上传文件API有以下几个步骤&#xff1a; 创建 <input type"file"> 元素&#xff0c;并监听文件选择事件 <input type"file" change"chooseFile">在 methods 中定义 chooseFile 方法&#xff0c;处理文件选择事件 met…

发起POST请求时同时携带文件和自定义参数

目录 背景 说明 背景 发送请求时需要携带文件&#xff0c;又想携带自定义参数的场景。 说明 需要带文件&#xff0c;则Content-Type已经确定&#xff1a; Content-Type: multipart/form-data" 其次在form中设置自己的参数&#xff1a; -F a1 注意此时-d&#xff08…

MySQL进阶2 - 索引

MySQL进阶1 - 索引 1. 索引概述2. 索引结构2.1 二叉树2.2 B-Tree(多路平衡查找树)2.3 BTree2.4 Hash 3. 索引分类4. 索引语法5. SQL性能分析5.1 SQL执行频率5.2 慢查询日志5.3 profile5.4 explain执行计划5.3.1 EXPLAIN执行计划各字段含义&#xff1a; 6. 索引使…

SpringBoot+FastJson 优雅的过滤 Response Body

Spring 源码系列 1、Spring 学习之扩展点总结之后置处理器&#xff08;一&#xff09; 2、Spring 学习之扩展点总结之后置处理器&#xff08;二&#xff09; 3、Spring 学习之扩展点总结之自定义事件&#xff08;三&#xff09; 4、Spring 学习之扩展点总结之内置事件&#xf…

手把手教你Linux查找Java的安装目录并设置环境变量以及Linux下执行javac未找到命令的保姆级教学

查找Java的安装目录 输入 java -version&#xff0c;查看是否成功安装Java 输入 which java&#xff0c;查看Java的执行路径 输入 ls -lrt /usr/bin/java 输入 ls -lrt /etc/alternatives/java&#xff0c;/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.242.b08-0.el7_7.x86_64 就是J…

自动化测试(终章)webdriver的常用api(2)以及新的开始

目录 多层框架/窗口定位 多层框架的定位 frame是什么&#xff1f; 多层窗口定位 层级定位 使用 XPath 进行层级定位&#xff1a; 使用 CSS 选择器进行层级定位&#xff1a; 下拉框处理 alert、confirm、prompt 的处理 Alert 弹窗&#xff1a; Confirm 弹窗&#xff…

推免那些事

平生第一次搞推免&#xff0c;也是最后一次。错失了一些机会&#xff0c;也有幸获得了一些机会&#xff0c;值得祝庆&#xff0c;也值得反思。 以下记录为个人流水账。 个人背景 我的背景可以算不是非常好了&#xff0c;况且今年211受歧视比较严重。 学校&#xff1a;211&…

IDEA安装插件搜索不到插件的解决方法

解决idea安装所需插件&#xff0c;插件搜索不到的问题 1.通过设置代理和去掉使用安装链接的方式来解决的 File–>Settings–>Appearance & Behavior–>System Settings—>HTTP Proxy(修改为图片所示) 2.在系统设置中将更新选项中的“use secure connection”…