这篇文章鸽了很久,我在这篇文章 《用好 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. linux shell cgi post,linux下shell处理cgi的方法--post get
  2. 阿里面试:索引失效的场景有哪些?索引何时会失效?
  3. 2019年春季学期第二周作业
  4. jQuery简单介绍
  5. 高级浏览器-SRWare Iron 29.0.1600.0 版本发布
  6. android 行布局选择器,『自定义View实战』—— 银行种类选择器
  7. python整数格式化表达式_Python字符串格式化表达式和格式化方法
  8. setyear java_如何在Java中创建不可变类
  9. linux下基于Posix message queue的同步消息队列的实现
  10. Maven 本地仓库访问私服
  11. Image.FormFile引起的若干问题
  12. Python3.x:pyodbc调用sybase的存储过程
  13. String 类实现 以及 流插入/流提取运算符重载
  14. 设备管理器里“SM总线控制器”、“其它PCI桥设备”驱动有问题
  15. laravel文件命名规范
  16. java基础-并发理论
  17. android点赞功能源码,Android实现朋友圈点赞列表
  18. 希腊字母含义及快捷键输入
  19. ES--Kibana相关操作创建索引和Mapping
  20. 怎样查询SCI和EI检索号

热门文章

  1. uniapp跨域两次请求解决方案
  2. mac下 home-brew安装及php,nginx环境安装及配置
  3. javascript中五种常见的DOM方法
  4. C++中依赖受限名称定义编译无法通过的问题
  5. sizeof()与_countof()用法
  6. 随想录(在x86 linux上仿真多核cpu运行)
  7. ae中心点重置工具_如何使用AE制作文字破碎动画?制作ae破碎文字特效教程分享...
  8. python str方法的用法_Python基础之str常用方法、for循环
  9. CVE-2021-34527: Windows Print Spooler 蠕虫级远程代码执行漏洞
  10. centos使用vnc实现远程访问图形化界面