统一异常处理解决方案

article/2025/9/13 22:39:29

💁 作者:小瓦匠
💖 欢迎关注我的个人公众号:小瓦匠学编程。微信号:xiaowajiangxbc
📢 本中涉及到的所有代码资源,可以在公众号中获取,关注并回复:源码下载
👉 本文涉及的源码资源在 coding-003-exception 项目中


你在目前负责的系统中,系统异常情况是如何处理的?还在使用try catch代码块来处理异常吗?这里介绍三种统一异常处理方案。

在上一篇:Java项目参数校验最佳实践中,用到了统一异常处理,但是当时并没有详细介绍,那么今天就带着大家一起来了解一下统一异常处理的常见解决方案,希望能对大家有所帮助。

丑陋的 try catch 代码块

对于异常处理来说,最简单直接的方式就是使用 try catch 代码块来捕获系统异常。代码如下:

@ApiOperation(value = "try catch 代码块")
@RequestMapping("unified/exception/tryCatch")
public R tryCatch(String bizCode) {try {log.info("tryCatch 入参:{}", bizCode);// do somethingreturn R.ok();} catch (Exception e) {log.error("业务执行失败,系统异常:{}", e.getMessage(), e);return R.error();}
}

但是这种处理方式需要我们编写大量的代码,而且异常信息不易于统一维护,增加了开发工作量,甚至可能还会出现异常没有被捕获的情况。为了能够高效的处理好各种系统异常,我们需要在项目中统一集中处理我们的异常。

统一异常处理

在 Spring 项目中,我们可以通过如下三种常见方案来实现全局统一异常处理。

  1. 基于 SpringBoot 的全局统一异常处理,需要实现 ErrorController 接口。
  2. 基于 Spring AOP 实现全局统一异常处理。
  3. 基于 @ControllerAdvice 注解实现 Controller 层全局统一异常处理。

使用统一异常处理的优点:

  • 标准统一的返回结果,系统交互更加友好
  • 有效防止业务异常没有被捕获的情况
  • 代码更加干净简洁,不需要开发者自己定义维护异常

基于SpringBoot的统一异常处理

通过实现 ErrorController 接口,来实现自定义错误异常返回。支持返回 JSON 字符串、自定义错误页面,可以做到根据不同 status 跳转不同的页面,代码示例:

@Slf4j
@RestController
@EnableConfigurationProperties({ServerProperties.class})
public class ExceptionController implements ErrorController {private static final String ERROR_PATH = "/error";private ErrorAttributes errorAttributes;@Autowiredprivate ServerProperties serverProperties;@Overridepublic String getErrorPath() {return ERROR_PATH;}@Autowiredpublic ExceptionController(ErrorAttributes errorAttributes) {this.errorAttributes = errorAttributes;}/*** web页面错误处理*/@RequestMapping(value = ERROR_PATH, produces = "text/html")@ResponseBodypublic ModelAndView errorHtml404(HttpServletRequest request, HttpServletResponse response) {ModelAndView modelAndView = null;ServletWebRequest requestAttributes = new ServletWebRequest(request);Map<String, Object> model = getErrorAttributes(requestAttributes, isIncludeStackTrace(request, MediaType.ALL));model.put("queryString", request.getQueryString());// 根据不同状态码返回不同页面,这里以404/500为例HttpStatus status = getStatus(request);if (status.value() == HttpStatus.INTERNAL_SERVER_ERROR.value()) { // 500modelAndView = new ModelAndView("500", model);} else if (status.value() == HttpStatus.NOT_FOUND.value()) { // 404modelAndView = new ModelAndView("404", model);} else {modelAndView = new ModelAndView("error", model);}return modelAndView;}/*** 除web页面外的错误处理,比如json/xml等*/@RequestMapping(value = ERROR_PATH)@ResponseBodypublic R errorApiHander(HttpServletRequest request) {ServletWebRequest requestAttributes = new ServletWebRequest(request);Map<String, Object> attr = getErrorAttributes(requestAttributes, isIncludeStackTrace(request, MediaType.ALL));HttpStatus status = getStatus(request);return R.error(status.value(), attr.get("message").toString());}/*** 确定是否应该包含 StackTrace 属性*/protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) {ErrorProperties.IncludeStacktrace include = this.serverProperties.getError().getIncludeStacktrace();if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {return true;}return include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM && getTraceParameter(request);}/*** 获取错误的信息*/private Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);}/*** 是否包含 trace*/private boolean getTraceParameter(HttpServletRequest request) {String parameter = request.getParameter("trace");return parameter != null && !"false".equalsIgnoreCase(parameter);}/*** 获取错误编码*/private HttpStatus getStatus(HttpServletRequest request) {Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");if (statusCode == null) {return HttpStatus.INTERNAL_SERVER_ERROR;}try {return HttpStatus.valueOf(statusCode);} catch (Exception ex) {log.error("获取当前 HttpStatus 发生异常", ex);return HttpStatus.INTERNAL_SERVER_ERROR;}}
}

我们可以根据不同异常状态码跳转不同页面,比如:400、403、404、500等异常,并且对于非 web 端请求可以返回 JSON 数据。但是它无法获取到异常的具体错误码,同时也无法根据异常类型进行不同的响应。

基于Spring AOP实现统一异常处理

首先使用 @Aspect 来声明一个切面,使用 @Pointcut 来定义切入点位置,然后使用 @Around 环绕通知来处理方法请求,当请求方法抛出异常后,使用 catch 捕获异常并通过 handlerException 方法处理异常信息。

通过上面的操作我们就可以实现异常的统一管理以及通过切面获取接口信息等。

@Slf4j
@Component
@Aspect
public class AspectException {@Pointcut("execution(* com.xwj.exception.solution_2.demo.controller.*.*(..))") // 切入点public void pointCut(){}@Around("pointCut()")public R handleControllerMethod(ProceedingJoinPoint pjp) {Stopwatch stopwatch = Stopwatch.createStarted();R r;try {log.info("执行Controller开始: " + pjp.getSignature() + " 参数:" + Lists.newArrayList(pjp.getArgs()).toString());r = (R) pjp.proceed(pjp.getArgs());log.info("执行Controller结束: " + pjp.getSignature() + ", 返回值:" + r.toString());log.info("耗时:" + stopwatch.stop().elapsed(TimeUnit.MILLISECONDS) + "(毫秒).");} catch (Throwable throwable) {r = handlerException(pjp, throwable);}return r;}/*** 处理异常信息*/private R handlerException(ProceedingJoinPoint pjp, Throwable e) {R r = null;if (e instanceof BusinessException) {BusinessException businessException = (BusinessException) e;log.error("BusinessException{方法:" + pjp.getSignature() + ", 参数:" + pjp.getArgs() + ",异常:" + businessException.getMessage() + "}", e);r = R.error(businessException.getCode(), businessException.getMessage());} else if (e instanceof RuntimeException) {log.error("RuntimeException{方法:" + pjp.getSignature() + ", 参数:" + pjp.getArgs() + ",异常:" + e.getMessage() + "}", e);r = R.error(400, "未知异常");} else {log.error("异常{方法:" + pjp.getSignature() + ", 参数:" + pjp.getArgs() + ",异常:" + e.getMessage() + "}", e);r = R.error(500, "系统异常");}return r;}
}

基于AOP原理实现的异常捕获,可以有效的捕获 controller、service 中出现的异常,而且还可以统一打印接口请求入参和返回结果日志,打印接口访问性能日志等,但是无法处理进入 controller 前出现的异常以及参数校验异常等情况。

@ControllerAdvice 统一异常处理

基于 @ControllerAdvice 注解实现的 Controller 层全局统一异常处理,同时还需要配合 @ExceptionHandler 注解一起使用。

@ControllerAdvice

作用于类上,使用该注解可以实现三个方面的功能:1. 全局异常处理;2. 全局数据绑定;3. 全局数据预处理。在项目中使用这个注解可以帮我们简化很多工作,它是 SpringMVC 提供的功能,并且在 SpringBoot 中也可以直接使用。

在进行全局异常处理时,需要配合 @ExceptionHandler 注解使用。

@RestControllerAdvice

同样也是作用于类上,它是 @ControllerAdvice 和 @ResponesBody 的合体,可以支持返回 JSON 格式的数据。在后面的代码示例中就会使用这个注解。

@ExceptionHandler

作用于方法上,顾明思议,它就是一个异常处理器,作用是统一处理某一类异常,可以很大程度的减少代码重复率和复杂度。该注解的 value 属性可以用于指定具体的拦截异常类型。

如果有多个 @ExceptionHandler 存在,则需要指定不同的 value 类型,由于异常类拥有继承关系,所以 @ExceptionHandler 会首先执行在继承树中靠前的异常类型。基于这个特性,我们可以使用 @ExceptionHandler 来处理程序中各种具体异常了,比如处理:

  1. ServletException,即进入 Controller 前的异常,如:
    NoHandlerFoundException、HttpRequestMethodNotSupportedException、HttpMediaTypeNotSupportedException等
  2. 基于特定业务的自定义业务异常,如:BusinessException、BaseException
  3. 参数校验异常,如:BindException、
    MethodArgumentNotValidException、ConstraintViolationException
  4. 未知异常,当上面的异常处理无法捕获某个异常时,统一使用 Throwable 来捕获,并响应为未知异常

统一异常处理类

@Slf4j
@RestControllerAdvice
public class UnifiedExceptionHandler {/*** 未知异常*/@ExceptionHandler(value = Throwable.class)public R handleException(Throwable t) {log.error("未知异常,{},异常类型:{}", t.getMessage(), t.getClass());return R.error();}/*** 业务异常*/@ExceptionHandler(value = BusinessException.class)public R handleBusinessException(BusinessException e) {log.error("业务处理异常,{}", e.getMessage(), e);return R.error(e.getCode(), e.getMessage());}/*** 进入 Controller 前的相关异常*/@ExceptionHandler({NoHandlerFoundException.class,HttpRequestMethodNotSupportedException.class,HttpMediaTypeNotSupportedException.class,HttpMediaTypeNotAcceptableException.class,MissingPathVariableException.class,MissingServletRequestParameterException.class,TypeMismatchException.class,HttpMessageNotReadableException.class,HttpMessageNotWritableException.class,ServletRequestBindingException.class,ConversionNotSupportedException.class,MissingServletRequestPartException.class,AsyncRequestTimeoutException.class})@ResponseBodypublic R handleServletException(Exception e) {log.error("网络请求异常,{}", e.getMessage(), e);return R.error(CommonResponseEnum.SERVLET_ERROR);}/*** 参数校验(Valid)异常*/@ExceptionHandler(value = {MethodArgumentNotValidException.class})public R handleValidException(MethodArgumentNotValidException e) {log.error("数据校验异常,{},异常类型:{}", e.getMessage(), e.getClass());BindingResult bindingResult = e.getBindingResult();Map<String, String> errorMap = getErrorMap(bindingResult);return R.error(CommonResponseEnum.PARAM_ERROR).put("data", errorMap);}/*** 参数绑定异常*/@ExceptionHandler(value = {BindException.class})public R handleValidException(BindException e) {log.error("数据校验异常,{},异常类型:{}", e.getMessage(), e.getClass());BindingResult bindingResult = e.getBindingResult();Map<String, String> errorMap = getErrorMap(bindingResult);return R.error(CommonResponseEnum.PARAM_ERROR).put("data", errorMap);}/*** 约束校验异常*/@ExceptionHandler(value = {ConstraintViolationException.class})public R handleValidException(ConstraintViolationException e) {log.error("数据校验异常,{},异常类型:{}", e.getMessage(), e.getClass());List<String> violations = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.toList());String error = violations.get(0);return R.error(CommonResponseEnum.PARAM_ERROR).put("data", error);}/*** 获取校验失败的结果*/private Map<String, String> getErrorMap(BindingResult result) {return result.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage, (k1, k2) -> k1));}/*** DataBinder 数据绑定访问器,集合参数校验时需要这个数据绑定*/@InitBinderprivate void activateDirectFieldAccess(DataBinder dataBinder) {dataBinder.initDirectFieldAccess();}
}

从上面的代码中可以看出,在不同异常情况下会响应不同的异常错误码,根据异常错误码我们可以快速定位系统问题。当然也可以根据异常类型响应HTTP状态码,推荐阅读:错误码应该如何设计?

无法捕获404异常
完成上面的工作后,在默认配置情况下,我发现404异常 (NoHandlerFoundException ),不会被统一异常处理器处理,经过翻阅相关资料发现,需要在项目配置文件中增加如下配置:

spring:mvc:throw-exception-if-no-handler-found: true # 表示当没有对应处理器时,允许抛出异常resources:add-mappings: false # 表示是否为静态资源添加对应的处理器

添加上述配置后就可以让404异常在统一异常处理器中生效了,详细的原因分析,在这里就不进行描述了,感兴趣的小伙伴找度娘。另外需要注意的是,如果你的项目中使用到了静态资源,那么请忽略上面的配置,否则会导致无法处理静态资源。

总结

上面介绍了目前主流的三种全局统一异常处理方案,每种处理方案都有各自的优缺点和适合场景,最后总结:

  1. ErrorController 接口实现类 虽然可以对全局错误进行处理,但是它无法获取到异常的具体错误码,同时也无法根据异常类型进行不同的响应。

  2. 使用 AOP 方案不仅可以做全局统一异常处理,还可以统一打印接口请求入参和返回结果日志,打印接口访问性能日志等。但是无法处理进入 controller 前出现的异常以及参数校验异常。

  3. @ControllerAdvice 配合 @ExceptionHandler 一起使用可以捕获全部异常情况,包括ServletException、参数校验异常、自定义业务异常、其他未知异常等,但是在默认情况下无法捕获 404 异常,需要在项目配置中进行额外处理。

到此我们也仅仅是介绍了项目中统一捕获异常信息的内容,关于如何优雅的判定并抛出异常,我会在下一篇中详细介绍。点击“关注”不迷路。


在这里插入图片描述
📌 学习 积累 沉淀 分享
💖 欢迎关注我的个人公众号:小瓦匠学编程! 微信号:xiaowajiangxbc
🔎 扫描二维码或微信搜索 “小瓦匠学编程” 即可关注。

(本文完)


http://chatgpt.dhexx.cn/article/4cyVvkxt.shtml

相关文章

SpringBoot统一异常处理详解

文章目录 一、概述1、统一异常处理介绍2、原理和目标 二、Assert(断言)1、概述2、Assert自定义实战2.1 自定义接口Assert2.2 自定义异常2.3 Enum整合2.4 实战检测 三、统一异常处理器1、异常处理器说明1.1 handleServletException1.2 handleBindException和handleValidExceptio…

Shell 异常处理

原创&#xff1a;转载请注明出处 #!/bin/bash ##################服务器执行以下脚本############################# ## 重新上传脚本到服务器 -> 部署启动的脚本#当任何一行的命令执行错误的时候&#xff08;比如命令写错了&#xff09;直接退出&#xff0c;不继续往下执行…

java中的统一异常处理

目录 统一异常处理的原因 如果进行统一异常处理 1、编写统一异常处理类与方法 2、编写自定义异常类 3、定义异常枚举类 4、抛出指定异常 小提醒 统一异常处理的原因 在我们写代码的时候&#xff0c;因为各种场景需要进行各种校验&#xff0c;我们就可能会进行多种响应&…

JNI异常处理

前言 本文所要介绍的异常处理是指通过JNI调用java层方法时产生的异常处理&#xff0c;并不是指JNI调用Native层函数时产生的异常处理&#xff0c;如果童鞋们想要了解Native层的异常处理可以参考笔者之前的文章《C之异常处理》 按照java的经验&#xff0c;当发生异常而又没有捕…

Python——异常处理

文章目录 异常Python中的异常类捕获与处理异常自定义异常类with语句断言 异常 异常是在程序执行过程中发生的影响程序正常执行的一个事件。异常是Python对象&#xff0c;当Python无法正常处理程序时就会抛出一个异常。一旦Python脚本发生异常&#xff0c;程序需要捕获并处理它…

python异常处理输入不是整数_Python异常处理

异常处理: Python程序运行语法出错会有异常抛出不处理异常会导致程序终止 示例:用户输入一个整数转换成int型,如果用户输入的不是数字而是其他例如字母等则会出现异常 不使用异常处理代码的处理方法 #cat 异常处理.py abc = input("请输入一个数字") if not ab…

ARM的异常处理机制

异常种类 ARM共有如下7种异常模式&#xff1a; 复位&#xff08;RESET)&#xff1a;当处理器复位引脚有效时&#xff0c;系统产生复位异常中断。复位异常中断通常在系统加电和系统复位时发生&#xff0c;直接跳转到复位中断向量处执行称为软复位。未定义的指令&#xff08;UDE…

spring异常处理

在项目中采用spring的异常处理机制&#xff1a; 示例一、在Controller中加ExceptionHandler注解定义异常拦截的方法&#xff0c;在方法中定义返回的页面&#xff1a; Controller public class ExceptionTestController {ExceptionHandlerpublic String handleException(Excep…

C语言异常处理

文章目录 前言一、 异常表达二、 异常报告三、 异常处理 前言 错误与异常&#xff1a; 错误与异常都是在程序编译或者运行时出现的错误&#xff0c; 不同的是&#xff0c;异常可以被开发人员捕捉和处理&#xff1b;而错误&#xff0c;一般不需要开发人员处理&#xff08;也无法…

SpringBoot全局异常处理

文章目录 异常处理方案分类基于请求转发基于异常处理器基于过滤器 常见异常处理实现方案1. BasicExceptionController2. ExceptionHandler3. ControllerAdviceExceptionHandler4. SimpleMappingExceptionResolver5. HandlerExceptionResolver6. Filter 全局异常处理实现方案1. …

springboot整合之统一异常处理

特别说明&#xff1a;本次项目整合基于idea进行的&#xff0c;如果使用Eclipse可能操作会略有不同&#xff0c;不过总的来说不影响。 springboot整合之如何选择版本及项目搭建 springboot整合之版本号统一管理 springboot整合mybatis-plusdurid数据库连接池 springboot整合…

C++ 异常处理

目录 一、异常的定义 二、异常的抛出和捕获 1.throw 2.try...catch 3.异常安全 4.异常规范 三、系统预定义异常 四、用户自定义异常 一、异常的定义 异常在C用于错误处理&#xff0c;C语言中一般使用返回值表示错误&#xff0c;C对错误处理进行了扩展&#xff0c;统一使…

关于异常处理的知识整理

目录 1.什么是异常&#xff1f; 2.异常继承结构 3.异常结构继承图 4.关于异常处理的两种方式&#xff1a; 5.异常对象有两个非常重要的方法 1.什么是异常&#xff1f; 程序在执行过程中不正常的情况称为异常&#xff0c;以类和对象的形势存在&#xff0c;可以通过异常类&am…

异常处理---

异常 异常处理 例&#xff1a; public class Demo{public static void main(String[] args) {System.out.println("开始");method();System.out.println("结束");}public static void method(){int[] arr{1,2,3};System.out.println(arr[3]);//数组索引…

异常处理

1.相关概念的介绍 1&#xff09;异常情形&#xff1a;是指阻止当前方法或作用域继续执行的问题。 2&#xff09;监控区域&#xff1a;一段可能产生异常的代码&#xff0c;并且后面跟着处理这些异常的代码。 3&#xff09;try块&#xff1a;如果在方法的内部或者在方法内部调…

异常及异常处理

一、异常的概念 定义&#xff1a; 异常指的是运行期出现的错误&#xff0c;也就是当程序开始执行以后执行期出现的错误。 处理态度&#xff1a; 当捕获到异常以后一定要做出处理&#xff0c;哪怕是把这个异常的错误信息打印出来&#xff0c;这是一种良好的编程习惯。 …

异常处理(throw、throws、try-catch)

Java异常处理的五个关键字try、catch、finally、throw、throws 1.抛出异常throw 在编写程序时&#xff0c;我们必须要考虑程序出现问题的情况。比如&#xff0c;在定义方法时&#xff0c;方法需要接收参数。那么&#xff0c;当调用方法使用接收到的参数时&#xff0c;首先需要…

SpringBoot+Axis2搭建WebService服务端

SpringBootAxis2搭建WebService服务端 之前用过Spring Axis2搭建过WebService项目&#xff0c;网上也有很多资料教程&#xff0c;最近需要在一个SpringBoot项目中添加Axis2的服务端&#xff0c;在网上找了很久&#xff0c;没有找到相关教程&#xff0c;最终经过大神朋友的指点…

AXI(2)完结

四、握手 4.1握手过程 所有的五个通道都是通过相同的VALID/READY握手处理来传输地址、数据和控制信息。 双向握手的机制意味着主机和从机之间传输数据时&#xff0c;都可以控制传输的速率&#xff0c;只有当VALID和READY同时为高电平时&#xff0c;传输才会发生。 发送方&a…

读Axis2用户帮助文档 (axis2中文文档)

作者&#xff1a;李红霞 时间&#xff1a;2006-10-19 声明&#xff1a;本文可以算作Axis2用户手册的翻译&#xff0c;但是翻译后的文本是经过作者理解写出来的&#xff0c;可能有些偏差&#xff0c;欢迎讨论。本文属作者原创&#xff0c;允许转载&#xff0c;但请注明出处。 英…