这篇文章鸽了很久,我在这篇文章 《用好 Java 中的枚举,真的没有那么简单!》 中就提到要分享。还是昨天一个读者提醒我之后,我才发现自己没有将这篇文章发到公众号。说到这里,我发现自己一个很大的问题,就是有时候在文章里面说要更新什么,结果后面就忘记了,很多时候不是自己没写,就因为各种事情混杂导致忘记发了。以后要尽量改正这个问题!

在上一篇文章《SpringBoot 处理异常的几种常见姿势》中我介绍了:

  1. 使用 @ControllerAdvice@ExceptionHandler 处理全局异常
  2. @ExceptionHandler 处理 Controller 级别的异常
  3. ResponseStatusException

通过这篇文章,可以搞懂如何在 Spring Boot 中进行异常处理。但是,光是会用了还不行,我们还要思考如何把异常处理这部分的代码写的稍微优雅一点。下面我会以我在工作中学到的一点实际项目中异常处理的方式,来说说我觉得稍微优雅点的异常处理解决方案。

下面仅仅是我作为一个我个人的角度来看的,如果各位读者有更好的解决方案或者觉得本文提出的方案还有优化的余地的话,欢迎在评论区评论。

最终效果展示

下面先来展示一下完成后的效果,当我们定义的异常被系统捕捉后返回给客户端的信息是这样的:

效果展示

返回的信息包含了异常下面 5 部分内容:

  1. 唯一标示异常的 code
  2. HTTP 状态码
  3. 错误路径
  4. 发生错误的时间戳
  5. 错误的具体信息

这样返回异常信息,更利于我们前端根据异常信息做出相应的表现。

异常处理核心代码

ErrorCode.java (此枚举类中包含了异常的唯一标识、HTTP 状态码以及错误信息)

这个类的主要作用就是统一管理系统中可能出现的异常,比较清晰明了。但是,可能出现的问题是当系统过于复杂,出现的异常过多之后,这个类会比较庞大。有一种解决办法:将多种相似的异常统一为一个,比如将用户找不到异常和订单信息未找到的异常都统一为“未找到该资源”这一种异常,然后前端再对相应的情况做详细处理(我个人的一种处理方法,不敢保证是比较好的一种做法)。

import org.springframework.http.HttpStatus;

public enum ErrorCode {

    RESOURCE_NOT_FOUND(1001, HttpStatus.NOT_FOUND, "未找到该资源"),    REQUEST_VALIDATION_FAILED(1002, HttpStatus.BAD_REQUEST, "请求数据格式验证失败");private final int code;

private final HttpStatus status;

private final String message;

    ErrorCode(int code, HttpStatus status, String message) {this.code = code;this.status = status;this.message = message;    }

public int getCode() {return code;    }

public HttpStatus getStatus() {return status;    }

public String getMessage() {return message;    }

@Overridepublic String toString() {return "ErrorCode{" +"code=" + code +", status=" + status +", message='" + message + '\'' +'}';    }}

ErrorReponse.java(返回给客户端具体的异常对象)

这个类作为异常信息返回给客户端,里面包括了当出现异常时我们想要返回给客户端的所有信息。

import org.springframework.util.ObjectUtils;

import java.time.Instant;import java.util.HashMap;import java.util.Map;

public class ErrorReponse {private int code;private int status;private String message;private String path;private Instant timestamp;private HashMap data = new HashMap();public ErrorReponse() {    }public ErrorReponse(BaseException ex, String path) {this(ex.getError().getCode(), ex.getError().getStatus().value(), ex.getError().getMessage(), path, ex.getData());    }public ErrorReponse(int code, int status, String message, String path, Map data) {this.code = code;this.status = status;this.message = message;this.path = path;this.timestamp = Instant.now();if (!ObjectUtils.isEmpty(data)) {this.data.putAll(data);        }    }// 省略 getter/setter 方法@Overridepublic String toString() {return "ErrorReponse{" +"code=" + code +", status=" + status +", message='" + message + '\'' +", path='" + path + '\'' +", timestamp=" + timestamp +", data=" + data +'}';    }}

BaseException.java(继承自 RuntimeException 的抽象类,可以看做系统中其他异常类的父类)

系统中的异常类都要继承自这个类。

public abstract class BaseException extends RuntimeException {private final ErrorCode error;private final HashMap data = new HashMap<>();public BaseException(ErrorCode error, Map data) {super(error.getMessage());this.error = error;if (!ObjectUtils.isEmpty(data)) {this.data.putAll(data);        }    }protected BaseException(ErrorCode error, Map data, Throwable cause) {super(error.getMessage(), cause);this.error = error;if (!ObjectUtils.isEmpty(data)) {this.data.putAll(data);        }    }public ErrorCode getError() {return error;    }public MapgetData() {return data;    }}

ResourceNotFoundException.java (自定义异常)

可以看出通过继承 BaseException 类我们自定义异常会变的非常简单!

import java.util.Map;

public class ResourceNotFoundException extends BaseException {

public ResourceNotFoundException(Map data) {super(ErrorCode.RESOURCE_NOT_FOUND, data);    }}

GlobalExceptionHandler.java(全局异常捕获)

我们定义了两个异常捕获方法。

这里再说明一下,实际上这个类只需要 handleAppException() 这一个方法就够了,因为它是本系统所有异常的父类。只要是抛出了继承 BaseException 类的异常后都会在这里被处理。

import com.twuc.webApp.web.ExceptionController;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;import org.springframework.web.bind.annotation.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpServletRequest;

@ControllerAdvice(assignableTypes = {ExceptionController.class})@ResponseBodypublic class GlobalExceptionHandler {

// 也可以将 BaseException 换为 RuntimeException// 因为 RuntimeException 是 BaseException 的父类@ExceptionHandler(BaseException.class)public ResponseEntity> handleAppException(BaseException ex, HttpServletRequest request) {        ErrorReponse representation = new ErrorReponse(ex, request.getRequestURI());return new ResponseEntity<>(representation, new HttpHeaders(), ex.getError().getStatus());    }

@ExceptionHandler(value = ResourceNotFoundException.class)public ResponseEntityhandleResourceNotFoundException(ResourceNotFoundException ex, HttpServletRequest request) {        ErrorReponse errorReponse = new ErrorReponse(ex, request.getRequestURI());return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errorReponse);    }}

(重要)一点扩展:

哈哈!实际上我多加了一个算是多余的异常捕获方法handleResourceNotFoundException() 主要是为了考考大家当我们抛出了 ResourceNotFoundException异常会被下面哪一个方法捕获呢?

答案:

会被handleResourceNotFoundException()方法捕获。因为 @ExceptionHandler 捕获异常的过程中,会优先找到最匹配的。

下面通过源码简单分析一下:

ExceptionHandlerMethodResolver.javagetMappedMethod决定了具体被哪个方法处理。

@Nullableprivate Method getMappedMethod(Class extends Throwable> exceptionType) {       List> matches = new ArrayList<>();//找到可以处理的所有异常信息。mappedMethods 中存放了异常和处理异常的方法的对应关系for (Class extends Throwable> mappedException : this.mappedMethods.keySet()) {if (mappedException.isAssignableFrom(exceptionType)) {              matches.add(mappedException);         }     }// 不为空说明有方法处理异常if (!matches.isEmpty()) {// 按照匹配程度从小到大排序          matches.sort(new ExceptionDepthComparator(exceptionType));// 返回处理异常的方法return this.mappedMethods.get(matches.get(0));      }else {return null;       } }

从源代码看出:getMappedMethod()会首先找到可以匹配处理异常的所有方法信息,然后对其进行从小到大的排序,最后取最小的那一个匹配的方法(即匹配度最高的那个)。

写一个抛出异常的类测试

Person.java

public class Person {private Long id;private String name;

// 省略 getter/setter 方法}

ExceptionController.java(抛出一场的类)

@RestController@RequestMapping("/api")public class ExceptionController {

@GetMapping("/resourceNotFound")public void throwException() {        Person p=new Person(1L,"SnailClimb");throw new ResourceNotFoundException(ImmutableMap.of("person id:", p.getId()));    }

}

源码地址:https://github.com/Snailclimb/springboot-guide/tree/master/source-code/basis/springboot-handle-exception-improved

推荐阅读

V1.1版JavaGuide面试突击来啦!这个更新真的太棒了! 面试官问我如何保证Kafka不丢失消息?我哭了! 如何准备大厂面试?如何变的更强?我咨询了身边的技术专家之后终于得到答案!好文让朋友知道你“在看”

springboot http status 404 – not found_使用枚举简单封装一个优雅的 Spring Boot 全局异常处理!...相关推荐

  1. Spring Boot 全局异常处理(400/404/500),顺便解决过滤器中异常未捕获到的问题,让RestApi 任何时候都能获取统一的格式代码

    出发点是为了在系统抛出异常的时候,前端仍然可以获取到统一的报文格式,所以后端所有的异常都得捕获,并处理 Spring boot 在处理异常的时候,500/404默认都会转发到/error,而这个异常的 ...

  2. spring boot入门(九) springboot的过滤器filter。最完整、简单易懂、详细的spring boot教程。

    关于过滤器和拦截器的区别,已经spring boot入门(七)中说明.下面举个过滤器的应用场景,比如用户信息页只有再用户登录后才可以进入,没有登录的用户是无法进入的,此时便可以采用过滤器来讲没有登录的 ...

  3. springboot 上传异常捕获_Spring Boot 全局异常处理(下)

    可以搜索微信公众号[Jet 与编程]查看更多精彩文章 背景 在上篇[链接]中介绍了 Spring Boot 全局异常处理的一种方式,但那是一种全局性的容错机制,目的是把 Spring Boot 默认的 ...

  4. springboot项目实战_2019学习进阶之路:高并发+性能优化+Spring boot等大型项目实战...

    Java架构师主要需要做哪些工作呢? 负责设计和搭建软件系统架构(平台.数据库.接口和应用架构等),解决开发中各种系统架构问题. 优化现有系统的性能,解决软件系统平台关键技术问题攻关.核心功能模块设计 ...

  5. SpringBoot项目中获取yml文件的属性时实体属性类出现Spring Boot Configuration Annotation Processor not found in classpath

    1.SpringBoot项目的项目结构如下: 2.属性实体类 上面出现了Spring Boot Configuration Annotation Processor not found in clas ...

  6. springboot有什么好的方案实现 数据实时更新吗?_使用Spring Boot Actuator、Jolokia和Grafana实现准实时监控...

    Spring Boot Actuator通过/metrics端点,以开箱即用的方式为应用程序的性能指标与响应统计提供了一个非常友好的监控方式. 由于在集群化的弹性环境中,应用程序的节点可以增长.扩展, ...

  7. springboot做网站_Github点赞接近 100k 的Spring Boot学习教程+实战项目推荐!

    " 本文已经收录进:awesome-java (Github 上非常棒的 Java 开源项目集合) 很明显的一个现象,除了一些老项目,现在 Java 后端项目基本都是基于 Spring Bo ...

  8. java 404页面_SpringBoot全局异常处理与定制404页面的方法

    一.错误处理原理分析 使用SpringBoot创建的web项目中,当我们请求的页面不存在(http状态码为404),或者器发生异常(http状态码一般为500)时,SpringBoot就会给我们返回错 ...

  9. java flux api,SpringBoot学习系列-WebFlux REST API 全局异常处理

    本文内容 为什么要全局异常处理? WebFlux REST 全局异常处理实战 小结 摘录:只有不断培养好习惯,同时不断打破坏习惯,我们的行为举止才能够自始至终都是正确的. 一.为什么要全局异常处理? ...

最新文章

  1. 什么是CPU的虚拟化技术?优势又是什么
  2. Swift2.0语言教程之类的方法
  3. 中山网络推广解答网站排名优化为什么总是上不了首页!
  4. 控制显示隐藏_iOS13隐藏了5个超实用新功能:让iPhone的使用体验更好
  5. Codeforces 603A Alternative Thinking
  6. “示范性高中”害死人呐!
  7. ElementUI中分页Pagination 样式的修改(分页组件的封装)
  8. 随想录(使用堆栈回溯函数调试代码)
  9. PLC编程软件等工具打包下载1.0【好用绿色三菱plc编程软件】
  10. 纤亿通带您了解生活小妙招--构建家庭网络指南
  11. thing JS笔记
  12. AI 工程师职业指南
  13. Cause: org.postgresql.util.PSQLException: 错误: 对于可变字符类型来说,值太长了(255)
  14. 例2-22 正弦信号的微分运算和积分运算
  15. 有关防火墙的调研总结
  16. 谈谈制造企业如何制定敏捷的数字化转型策略
  17. java输出月份对应的季节
  18. 2022春雷视频开源项目白皮书
  19. https双向认证訪问管理后台,採用USBKEY进行系统訪问的身份鉴别,KEY的证书长度大于128位,使用USBKEY登录...
  20. Mac 上解压 tgz 会报错,但在 Linux 上却没有,解决办法

热门文章

  1. 京东宙斯平台使用方法(accesstoken,appkey,appsecret参数和SDK的获取)
  2. 华东理工大学和暨南大学计算机考研,2016华东理工大学VS暨南大学 孰强孰弱?...
  3. vue php 加载速度,Vue加载优化,速度提高一倍。
  4. reprint函数的用法c语言,百篇大计敬本年之C语言巅峰之道《五》—— snprintf 是否需要 memset 缓冲区...
  5. python打印字符串的前五行_python基础-字符串(6)
  6. iphone换机数据迁移_怎么一键换机?换新机迁移数据必看教程!
  7. 苹果付费app共享公众号_娄底共享云店铺公众号
  8. laravel mysql like_3分钟短文|Laravel 使用like匹配字符串的用法示例
  9. Windows系统 services.msc命令详解,Windows命令行查看本地服务
  10. redhat常用基础命令代码