关于如何创建一个可配置的 SpringBoot Web 项目的全局异常处理

news2024/11/19 21:31:27

前情概要

这个问题其实困扰了我一周时间,一周都在 Google 上旅游,我要如何动态的设置 @RestControllerAdvice 里面的 basePackages 以及 baseClasses 的值呢?经过一周的时间寻求无果之后打算决定放弃的我终于找到了一些关键的线索。
当然在此也感激这篇文章:@ControllerAdvice的用法和原理探究
其实我们只要有调试源码的习惯,也能够发现这些东西,可能有时候就是差那么一点动力吧,比如我,就想直接看现成的解析,没有那么主动去调试源码,哈哈哈。

发现了关键问题之后

其实从上面这篇文章里我提取到的关键信息如下:

@Bean
public HandlerExceptionResolver handlerExceptionResolver(
		@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
	List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
	configureHandlerExceptionResolvers(exceptionResolvers);
	if (exceptionResolvers.isEmpty()) {
		addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
	}
	extendHandlerExceptionResolvers(exceptionResolvers);
	HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
	composite.setOrder(0);
	composite.setExceptionResolvers(exceptionResolvers);
	return composite;
}

PS:😓 后来我才发现这里面的 configureHandlerExceptionResolversaddDefaultHandlerExceptionResolvers extendHandlerExceptionResolvers 这三个方法是源码里面的,我还一直在找这个博主有关这两个方法的实现。

OK 回来,这里面的关键就是我需要往 Spring IOC 里面添加一个 HandlerExceptionResolverComposite ,并且设置它的处理器列表就好了。
于是我就顺着这个思路开始捣鼓,OK,下面是第一个版本的代码:

版本一

Starter 配置类(关键代码)

@Bean
public HandlerExceptionResolver handlerExceptionResolver() {
  final HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
  final List<HandlerExceptionResolver> resolves =
      Collections.singletonList(new RestGlobalExceptionHandler(starterProperties));
  composite.setOrder(0);
  composite.setExceptionResolvers(resolves);
  return composite;
}

Handler 处理器类(关键代码)

public final class RestGlobalExceptionHandler extends DefaultHandlerExceptionResolver implements
    HandlerExceptionResolver {

  @Override
  public ModelAndView resolveException(
      final HttpServletRequest request,
      final HttpServletResponse response,
      final Object handler,
      final Exception ex) {
    ModelAndView view = new ModelAndView();
    if (ex instanceof BusinessException) {
      printToResponse(response, handlerError(request, (BusinessException) ex));
    } else if (ex instanceof MethodArgumentNotValidException) {
      printToResponse(response, handlerError(request, (MethodArgumentNotValidException) ex));
    } else {
      // use default exception handler
      view = super.doResolveException(request, response, handler, ex);
    }
    if (Objects.isNull(view)) {
      // use finally exception handler
      view = new ModelAndView();
      printToResponse(response, handlerError(request, ex));
    }
    return view;
  }
  // handlerError 以及 printToResponse 方法省略
}

这里说下为什么就要继承 DefaultHandlerExceptionResolver以及实现HandlerExceptionResolver接口:
实现接口:因为 HandlerExceptionResolverComposite类的 resolves 列表就是一个List<HandlerExceptionResolver>, 所以我们自定义的 Handler 需要实现这个接口。
继承 DefaultHandlerExceptionResolver类:因为可以看到我重写了 resolveException这个方法

但是这还存在问题:

// 这个是 DefaultHandlerExceptionResolver 的 doResolveException 方法,你们实践第一版的时候会发现,有一些异常信息被这方法处理了,但是我发现如果使用 @RestControllerAdvice 结合 @ExceptionHandler 的话,优先是考虑我们自定义的异常处理的,于是有了下面的版本二。
@Nullable
  protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
    try {
      if (ex instanceof HttpRequestMethodNotSupportedException) {
        return this.handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException)ex, request, response, handler);
      }

      if (ex instanceof HttpMediaTypeNotSupportedException) {
        return this.handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException)ex, request, response, handler);
      }

      if (ex instanceof HttpMediaTypeNotAcceptableException) {
        return this.handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException)ex, request, response, handler);
      }

      if (ex instanceof MissingPathVariableException) {
        return this.handleMissingPathVariable((MissingPathVariableException)ex, request, response, handler);
      }

      if (ex instanceof MissingServletRequestParameterException) {
        return this.handleMissingServletRequestParameter((MissingServletRequestParameterException)ex, request, response, handler);
      }

      if (ex instanceof ServletRequestBindingException) {
        return this.handleServletRequestBindingException((ServletRequestBindingException)ex, request, response, handler);
      }

      if (ex instanceof ConversionNotSupportedException) {
        return this.handleConversionNotSupported((ConversionNotSupportedException)ex, request, response, handler);
      }

      if (ex instanceof TypeMismatchException) {
        return this.handleTypeMismatch((TypeMismatchException)ex, request, response, handler);
      }

      if (ex instanceof HttpMessageNotReadableException) {
        return this.handleHttpMessageNotReadable((HttpMessageNotReadableException)ex, request, response, handler);
      }

      if (ex instanceof HttpMessageNotWritableException) {
        return this.handleHttpMessageNotWritable((HttpMessageNotWritableException)ex, request, response, handler);
      }

      if (ex instanceof MethodArgumentNotValidException) {
        return this.handleMethodArgumentNotValidException((MethodArgumentNotValidException)ex, request, response, handler);
      }

      if (ex instanceof MissingServletRequestPartException) {
        return this.handleMissingServletRequestPartException((MissingServletRequestPartException)ex, request, response, handler);
      }

      if (ex instanceof BindException) {
        return this.handleBindException((BindException)ex, request, response, handler);
      }

      if (ex instanceof NoHandlerFoundException) {
        return this.handleNoHandlerFoundException((NoHandlerFoundException)ex, request, response, handler);
      }

      if (ex instanceof AsyncRequestTimeoutException) {
        return this.handleAsyncRequestTimeoutException((AsyncRequestTimeoutException)ex, request, response, handler);
      }
    } catch (Exception var6) {
      Exception handlerEx = var6;
      if (this.logger.isWarnEnabled()) {
        this.logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx);
      }
    }

    return null;
  }

版本二

这里主要解决的是,有一些自定义的异常处理提前被 DefaultHandlerExceptionResolver 类的 doResolveException 的方法处理了,了解 Spring IOC 容器的小伙伴应该都知道,这很容易让我们联想起,Bean 的顺序问题,那么我们哪里设置了我们自定义 Bean 的顺序呢,也就是使用 @Order 或者代码里面设置了,没错,就是这里:

@Bean
public HandlerExceptionResolver handlerExceptionResolver() {
  final HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
  final List<HandlerExceptionResolver> resolves =
      Collections.singletonList(new RestGlobalExceptionHandler(starterProperties));
  // 这里,这里,这里
  composite.setOrder(0);
  composite.setExceptionResolvers(resolves);
  return composite;
}

但是我们要怎么知道我们具体要设置的值是什么呢?大家都知道在使用 Spring 的时候设置 Bean 的 Order 值越小那么它的优先级越高,所以我尝试着把 0 换成 -1,试试,结果还真成功了,我们自定义的优先执行了,但是作为一个搞技术的人,还是想摸清楚它的原理吧,为啥设置成 -1 就可以了呢?
一开始没有啥头绪,不知道怎么去查找 Spring 配置异常处理这块( 主要还是源码不熟 -_-! ),但后面想一想,把目标换一下不就好了,是因为 DefaultHandlerExceptionResolver 这个 Bean 的原因,那我就找 Spring 是哪里把他放进 Spring IOC 容器的不就好了。因为我使用的框架是 SpringBoot,所以相关的配置肯定在 XXXAutoConfiguration 类里面,于是就找找找…

  • WebMvcAutoConfiguration 没有…
  • DispatcherServletAutoConfiguration 没有…

好吧最后还是借助 IntelliJ 这个工具,因为我们自定义的是一个 HandlerExceptionResolverComposite Bean,所以我们进入这个类:
在这里插入图片描述
发现它实现了 HandlerExceptionResolver 这个接口,一般 Spring 都会使用接口作为一个接收实现的变量,然后 return 回去交给 Spring IOC 容器,所以我们再进入这个接口:
在这里插入图片描述
利用 IntelliJ 的工具,找到哪里注入了这个 Bean:
在这里插入图片描述
在这里插入图片描述
进去之后发现了跟我们类似的代码:
在这里插入图片描述
看到 618 行的 this.addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager); 吗?这个就是添加 DefaultHandlerExceptionResolver 的地方,并且设置的 Order 是 0,如果 Debug 的话你会发现这个方法除了加入 DefaultHandlerExceptionResolver 之外,还会加入自定义使用注解的异常处理器,但是它在 List 中的顺序比 DefaultHandlerExceptionResolver 靠前,所以它会优先使用自定义的处理器处理,但是使用注解是 Spring 自己处理的,然后加入这个 List 中,我们没办法去修改这个 List,但是我们知道了可以自定义 HandlerExceptionResolverComposite 并且把它的 Order 设置成比 0 小就好了,所以这就是为什么我们设置成 -1 就可以覆盖 DefaultHandlerExceptionResolver 的行为的原因。

所有的代码已经放在我的代码仓库:码云,欢迎来访以及给我小⭐⭐

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

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

相关文章

鸿蒙 DevEcoStudio:通知栏通知实现

【使用notificationManager实现通知栏功能】 【普通通知、长文本通知、多行通知、图片通知】 import notificationManager from ohos.notificationManager import image from ohos.multimedia.image Entry Component struct Index {State message: string Hello World// 将图…

网络安全之BGP详解

BGP&#xff1b;边界网关协议 使用范围&#xff1b;BGP范围&#xff0c;在AS之间使用的协议。 协议的特点&#xff08;算法&#xff09;&#xff1a;路径矢量型&#xff0c;没有算法。 协议是否传递网络掩码&#xff1a;传递网络掩码&#xff0c;支持VLSM&#xff0c;CIDR …

17款奔驰GLS450升级头等舱行政独立四座马鞍是什么样体验

五座版本&#xff1a;迈巴赫GLS480的五座版本通常指的是具有五个座位的配置&#xff0c;包括两个前排座椅和三个后排座椅。这种配置适合搭载更多乘客&#xff0c;后排座椅通常为三人座设计&#xff0c;乘坐人数较多。 四座版本&#xff1a;迈巴赫GLS480的四座版本通常指的是具…

基于地理坐标的高阶几何编辑工具算法(4)——线分割面

文章目录 工具步骤应用场景算法输入算法输出算法示意图算法原理 工具步骤 选中待分割面&#xff0c;点击“线分割面”工具&#xff0c;绘制和面至少两个交点的线&#xff0c;双击结束&#xff0c;执行分割操作 应用场景 快速切分大型几何面&#xff0c;以降低面的复杂度&…

云曦2024年春季学期期中考复现

目录 Web Web_SINGIN 简简单单的文件上传 好玩的PHP 渗透的本质 简简单单的sql re baby_re easy xor Crypto easy_rsa Rsa2 Crypto_Singin Pwn pwn_Sing Misc easy_singin Xjpg 流量分析1 流量分析3 流量分析2 Web Web_SINGIN 1.使用右键检查&#xff0c…

如何在没有密码或Face ID的情况下解锁iPhone

iPhone 是一款以其一流的安全功能而闻名的设备&#xff0c;包括面容 ID 和密码。但是&#xff0c;你有没有想过&#xff0c;如果没有这些安全措施&#xff0c;你是否可以解锁iPhone&#xff1f;无论您是忘记了密码&#xff0c;Face ID不起作用&#xff0c;还是只是对其他方法感…

提权方式及原理汇总

一、Linux提权 1、SUID提权 SUID&#xff08;设置用户ID&#xff09;是赋予文件的一种权限&#xff0c;它会出现在文件拥有者权限的执行位上&#xff0c;具有这种权限的文件会在其执行时&#xff0c;使调用者暂时获得该文件拥有者的权限。 为可执行文件添加suid权限的目的是简…

VUE使用screenfull实现网页全屏显示

使用方法 首先安装 npm install screenfull --save在使用.vue文件中 引入 import screenfull from screenfull在按钮方法中调用 screenfull.toggle()还可以检测全屏状态 screenfull.isFullscreen测试浏览器是否支持全screenfull screenfull.isEnabled API .request(ele) 全屏…

谓词逻辑(一)

一、句子的谓词符号化 谓词逻辑&#xff0c;也叫一阶逻辑&#xff0c;它对每个最简单的命题尽一步进行分解。 1个体词&#xff1a;可以独立存在的客体。 2谓词&#xff1a;描述一个个体词的属性或多个个体词之间的关系&#xff08;可用一元函数和多元函数来理解&#xff09;…

基于树的存储数据结构demo

一.简介 由于之前博主尝试Java重构redis&#xff0c;在redis中的的字典数据结构底层也是采用数组实现&#xff0c;字典中存在两个hash表&#xff0c;一个是用于存储数据&#xff0c;另一个被用于rehash扩容为前者两倍。但是我注意到了在redis的数据结构中&#xff0c;并没有像…

linux系统硬盘读写慢的排查方法

如果服务器硬盘读写慢&#xff0c;可能会导致处理性能降低&#xff0c;用户响应慢。因此及时排除故障至关重要。下面是硬盘读写慢的排查思路。 1、top命令查看硬盘是否繁忙。 2、找出占用硬盘带宽高的进程。 通过iotop命令进行查看&#xff0c;iotop命令是用于展示硬盘读写操作…

高中数学:平面向量-数乘运算

一、定义 顾名思义 向量的数乘运算&#xff0c;就是数量与向量相乘的运算 λ a → \mathop{a}\limits ^{\rightarrow} a→&#xff0c;λ∈R 二、λ a → \mathop{a}\limits ^{\rightarrow} a→的性质 1、长度 |λ|*| a → \mathop{a}\limits ^{\rightarrow} a→| |λ a …

设计模式6——单例模式

写文章的初心主要是用来帮助自己快速的回忆这个模式该怎么用&#xff0c;主要是下面的UML图可以起到大作用&#xff0c;在你学习过一遍以后可能会遗忘&#xff0c;忘记了不要紧&#xff0c;只要看一眼UML图就能想起来了。同时也请大家多多指教。 单例模式&#xff08;Singleto…

python科研数据可视化之折线图

例如 &#xff1a; 下面的配色表画出的图很好看。选择喜欢的颜色&#xff0c;找到代码中颜色部分进行修改即可。 代码部分已经有详细的注释&#xff0c;就不一一解释了。另外&#xff0c;如果想要坐标轴从设定的值开始就把下面代码中的范围xlim&#xff0c;ylim进行注释。 imp…

Goby 漏洞发布|Sonatype Nexus Repository Manager 文件读取漏洞(CVE-2024-4956)

漏洞名称&#xff1a;Sonatype Nexus Repository Manager 文件读取漏洞&#xff08;CVE-2024-4956&#xff09; English Name&#xff1a; Sonatype Nexus Repository Manager File Read Vulnerability(CVE-2024-4956) CVSS core: 7.5 影响资产数&#xff1a;93784 漏洞描述…

QT--TCP网络通讯工具编写记录

QT–TCP网络通讯工具编写记录 文章目录 QT--TCP网络通讯工具编写记录前言演示如下&#xff1a;一、服务端项目文件&#xff1a;【1.1】server_tcp.h 服务端声明文件【1.2】thread_1.h 线程处理声明文件【1.3】main.cpp 执行源文件【1.4】server_tcp.cpp 服务端逻辑实现源文件【…

开箱测评!吸猫毛除味神器,希喂FreAir Lite宠物空气净化器实测

掉毛季又来了&#xff0c;猫咪的毛发满天飞&#xff0c;怎么办&#xff1f;我家掉毛怪一到季节就开始掉老多毛&#xff0c;关键还喜欢在家里打架跑酷&#xff01;天上地下都是毛&#xff01;为了减少家里空气中浮毛&#xff0c;你做过那些努力呢&#xff1f;最近猫掉毛掉的&…

02-结构型设计模式(共7种)

上一篇&#xff1a;01-创建型设计模式(共6种) 1. Adapter(适配器模式) 适配器模式是一种结构型设计模式&#xff0c;它允许将一个类的接口转换成客户端所期望的另一个接口。这种模式通常用于解决接口不兼容的情况&#xff0c;使得原本由于接口不匹配而无法工作的类可以一起工作…

20232810 2023-2024-2 《网络攻防实践》实验十

1.实践内容 1.1SEED SQL注入攻击与防御实验 &#xff08;1&#xff09;使用数据库熟悉SQL查询&#xff1a; 我们已经创建了一个名为Users的数据库&#xff0c;其中包含一个名为creditential的表。该表存储了每个员工的个人信息&#xff08;例如&#xff0c;eid&#xff0c;密…