spring boot / cloud (十二) 异常统一处理进阶

前言

spring boot / cloud (二) 规范响应格式以及统一异常处理这篇博客中已经提到了使用@ExceptionHandler来处理各种类型的异常,这种方式也是互联网上广泛的方式

今天这篇博客,将介绍一种spring boot官方文档上的统一处理异常的方式.大家可以在spring boot 官方文档查看介绍

在开始介绍新的方法之前 , 我们先来分析一下 , 以前的做法有那些地方是需要优化的

场景分析

通常我们需要做统一异常处理的需求,大概都是要规范异常输出,以及处理,通同一套抽象出来的逻辑来处理所有异常.

但是在当前流行RestFul风格接口的环境下,对异常的输出还做了额外的一个要求,就是针对不同的错误需要输出对应的http状态.

在前面的实现中,我们大可以指定一个处理Exception的@ExceptionHandler,这样所有异常都能囊括了,但是却无法很好的将http状态区分开来.

如果要实现不同的异常输出不同的http状态,在原来的做法里就要将每个异常都穷举出来,然后做不同的设定.

显然,我们是不希望这样做的,显得太不聪明,不过还好,spring已经帮我们把这一步已经做掉了,我们只需处理自己关心的异常即可

@ExceptionHandler(value = 要拦截的异常.class)
@ResponseStatus(响应状态)
@ResponseBody
public RestResponse<String> exception(要拦截的异常 exception) {
return new RestResponse<>(ErrorCode.ERROR, buildError(exception));
} @ExceptionHandler(value = Exception.class)
@ResponseStatus(500)
@ResponseBody
public RestResponse<String> exception(Exception exception) {
return new RestResponse<>(ErrorCode.ERROR, buildError(exception));
}

源码解读

官方文档中指出,你需要实现一个类,使用@ControllerAdvice标注,然后继承至ResponseEntityExceptionHandler类.

这个ResponseEntityExceptionHandler类是一个抽象类,如下是它的核心方法

@ExceptionHandler({
NoSuchRequestHandlingMethodException.class,
HttpRequestMethodNotSupportedException.class,
.....省略
})
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) {
HttpHeaders headers = new HttpHeaders();
if (ex instanceof NoSuchRequestHandlingMethodException) {
HttpStatus status = HttpStatus.NOT_FOUND;
return handleNoSuchRequestHandlingMethod(
(NoSuchRequestHandlingMethodException) ex,
headers, status, request);
}
else if (ex instanceof HttpRequestMethodNotSupportedException) {
HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED;
return handleHttpRequestMethodNotSupported(
(HttpRequestMethodNotSupportedException) ex, headers, status, request);
} else if (..............){
.....省略
} else {
.....省略
return handleExceptionInternal(ex, null, headers, status, request);
}
}

在以上的代码片段中,我们可以看到handleException方法已经把常见的异常都拦截掉了,并且做出了适当的处理,并且在最后,else分支里,调用了handleExceptionInternal方法,

这个方法就是处理没有被拦截到的异常,然后这也是我们要进行扩展的地方

实现

实现ExceptionHandle类,继承至ResponseEntityExceptionHandler,并且注解@ControllerAdvice

@ControllerAdvice
@Slf4j
public class ExceptionHandle extends ResponseEntityExceptionHandler {
.....
}

实现exception方法,使用@ExceptionHandler拦截Exception,那么在这里,所有的异常都会进入这个方法进行处理.

然后调用父类的handleException方法(上面提到的),让spring默认的异常处理先处理一遍,如果当前的异常恰巧是被spring拦截的,那么就用spring的默认实现处理,就无需在写额外的代码了,http状态码也一并的会设置好.

最后在调用我们即将要重写的方法handleExceptionInternal,来处理自定义异常以及规范异常输出

@ExceptionHandler(value = Exception.class)
public ResponseEntity<Object> exception(Exception ex, WebRequest request) {
ResponseEntity<Object> objectResponseEntity = this.handleException(ex, request);
return this.handleExceptionInternal(ex, null, objectResponseEntity.getHeaders(), objectResponseEntity.getStatusCode(), request);
}

重写handleExceptionInternal方法,

在这个方法里面,可以向如下实现一样,去处理项目中自定义的异常,将其规范为想要的输出格式,

最后再调用父类的handleExceptionInternal方法,将控制权交还给spring,

这样就完成了整个异常处理的流程

@Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
HttpStatus localHttpStatus = status;
ErrorResult errorResult = buildError(applicationConfig, ex);
if (ex instanceof PermissionException) { //权限异常
localHttpStatus = HttpStatus.FORBIDDEN;
} else if (ex instanceof AuthException) { //认证异常
localHttpStatus = HttpStatus.UNAUTHORIZED;
} else if (ex instanceof ParameterValidException) { //参数校验异常
localHttpStatus = HttpStatus.BAD_REQUEST;
} else if (ex instanceof RestClientResponseException) { //rest请求异常
try {
RestClientResponseException restClientResponseException = (RestClientResponseException) ex;
String data = restClientResponseException.getResponseBodyAsString();
if (StringUtils.isNotBlank(data)) {
RestResponse<String> child = objectMapper.readValue(data, objectMapper.getTypeFactory().constructParametricType(RestResponse.class, String.class));
errorResult.setChild(child);
}
} catch (IOException e) {
throw new SystemRuntimeException(e);
}
}
log.error(ex.getClass().getName(), ex);
return super.handleExceptionInternal(ex, new RestResponse<>(localHttpStatus, errorResult), headers, localHttpStatus, request);
}

结束

在上面我们优化了统一异常处理的代码,做到了只关心系统自定义异常的处理,框架和容器的异常处理,交由spring处理,简化了代码,避免了重复造轮子,同时代码也更加健壮了.

在下一篇文章中,我会介绍另外一个更合里的处理404错误的方式,敬请期待

代码仓库 (博客配套代码)


想获得最快更新,请关注公众号

spring boot / cloud (十二) 异常统一处理进阶的更多相关文章

  1. spring boot / cloud (十五) 分布式调度中心进阶

    spring boot / cloud (十五) 分布式调度中心进阶 在<spring boot / cloud (十) 使用quartz搭建调度中心>这篇文章中介绍了如何在spring ...

  2. spring boot / cloud (十六) 分布式ID生成服务

    spring boot / cloud (十六) 分布式ID生成服务 在几乎所有的分布式系统或者采用了分库/分表设计的系统中,几乎都会需要生成数据的唯一标识ID的需求, 常规做法,是使用数据库中的自动 ...

  3. spring boot / cloud (十九) 并发消费消息,如何保证入库的数据是最新的?

    spring boot / cloud (十九) 并发消费消息,如何保证入库的数据是最新的? 消息中间件在解决异步处理,模块间解耦和,和高流量场景的削峰,等情况下有着很广泛的应用 . 本文将跟大家一起 ...

  4. spring boot / cloud (十四) 微服务间远程服务调用的认证和鉴权的思考和设计,以及restFul风格的url匹配拦截方法

    spring boot / cloud (十四) 微服务间远程服务调用的认证和鉴权的思考和设计,以及restFul风格的url匹配拦截方法 前言 本篇接着<spring boot / cloud ...

  5. spring boot / cloud (十八) 使用docker快速搭建本地环境

    spring boot / cloud (十八) 使用docker快速搭建本地环境 在平时的开发中工作中,环境的搭建其实一直都是一个很麻烦的事情 特别是现在,系统越来越复杂,所需要连接的一些中间件也越 ...

  6. Spring Boot(十二)单元测试JUnit

    一.介绍 JUnit是一款优秀的开源Java单元测试框架,也是目前使用率最高最流行的测试框架,开发工具Eclipse和IDEA对JUnit都有很好的支持,JUnit主要用于白盒测试和回归测试. 白盒测 ...

  7. Spring Boot (十二): Spring Boot 邮件服务

    最早我们发邮件的时候是使用 JavaMail 来发送邮件,而在 Spring Boot 中, Spring Boot 帮我们将 JavaMail 封装好了,是可以直接拿来使用的. 1. 依赖文件 po ...

  8. Spring Boot(十二):LocalDateTime格式化处理

    Java 8之后,日期类的处理建议使用java.time包中对应的LocalDateTime, LocalDate, LocalTime类.(参考Java8新特性) 在Spring Boot中(验证版 ...

  9. spring boot 学习(十二)拦截器实现IP黑名单

    拦截器实现IP黑名单 前言 最近一直在搞 Hexo+GithubPage 搭建个人博客,所以没怎么进行 SpringBoot 的学习.所以今天就将上次的”?秒防刷新”进行了一番修改.上次是采用注解加拦 ...

随机推荐

  1. 智慧航空AI大赛-阿里云算法大赛总结 第一赛季总结

    [以前的文章]最后一公里极速配送 - 阿里云算法大赛总结 总结一下新的教训 1.由于都是NP难题,获得最优解用常规的方法非常困难,对于不是算法科班出身的人来说,首先应该到网络上寻找一下论文,是否有一些 ...

  2. Javascript之布尔

    一.概念 将非布尔值转成布尔值(true)或(false). 二.创建布尔对象 var bool = new Boolean(); console.log(bool);//Boolean { fals ...

  3. 花了一年时间完成的 在线G代码编辑,加工系统 G-Code Editor V1.0

    G代码是数控程序中的加工指令.一般都称为G指令.可以直接用来驱动机床,各种控制系统.是一种数控行业标准.传统的G代码编写以及编辑无法在线编辑,也不能实时看到g代码编辑的最后加工路径已经不能直接对编辑的 ...

  4. 【模板小程序】链表排序(qsort/insert_sort/merge_sort)

    前言 本文章整理了链表排序的三种方法,分别是快速排序.插入排序.归并排序.为适应不同用途,先给出常用的int版本,再在此基础上抽象出类模板. 目录 一.针对整数的版本(常用) 文中链表定义 链表相关操 ...

  5. 为什么是Spring Boot

    原文:https://dzone.com/articles/why-springboot 作者:Siva Prasad Reddy Katamreddy 译者:Oopsguy 本文介绍将各种Sprin ...

  6. Spring+SpringMVC+MyBatis+easyUI整合基础篇(四)代码简化

    点这里看实际效果! 账密:admin 123456   敲了一天的代码,有些烦,感觉前一篇文章写的太笼统了,哈哈哈.   本来呢,也就是刚开始写,所以很多细节都想不到,源码也放上来了,自己动动手应该也 ...

  7. akoj-1074-人见人爱A^B

    人见人爱A^B Time Limit:1000MS  Memory Limit:65536K Total Submit:91 Accepted:55 Description 求A^B的最后三位数表示的 ...

  8. 敏捷视界:Scrum起源、Scrum术语

    Scrum起源 Scrum的原始含义 Scrum原始含义是指英式橄榄球次要犯规时在犯规地点对阵争球.争球双方各有8个队员参与,各方出3名前锋队员,并肩各站成一横排,面对面躬身互相顶肩,中间形成一条通道 ...

  9. 2017年05月10日记一次微项目投产 | 安卓版微信内置浏览器不能解析gzip压缩过的mp4视频的问题

    前言 今天投产了一个小项目,一个很简单的H5,有播放视频功能,使用了videojs插件. 之前也做过数个视频播放,视频的转压都按照既定流程进行,文件放到FTP后,iphone和安卓机测试下来都没有问题 ...

  10. 【有意思的BUG】未名

    这个帖子描述定位一个BUG的思路. 开始了. 用浏览器访问某一个网址http://111.aaa.com/ ,如果发现提示异常,那么接下来该如何定位BUG呢? 用相同的浏览器去访问不同域(不是aaa. ...