From 81ff703c1f82a952a4af0a35b6e14a112c3df5a4 Mon Sep 17 00:00:00 2001 From: wangkang Date: Wed, 23 Aug 2017 13:49:08 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=8D=9A=E5=AE=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 + md/12.md | 159 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 md/12.md diff --git a/README.md b/README.md index 3e6250b..832e5a1 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ ## 项目博客 (点击跳转) +- [spring boot / cloud (十二) 异常统一处理进阶](https://my.oschina.net/wangkang80/blog/1519189) + - [spring boot / cloud (十一) 回归-博客配套项目开源](https://my.oschina.net/wangkang80/blog/1517938) - [spring boot / cloud (十) 使用quartz搭建调度中心](https://my.oschina.net/wangkang80/blog/983208) diff --git a/md/12.md b/md/12.md new file mode 100644 index 0000000..fc43a5c --- /dev/null +++ b/md/12.md @@ -0,0 +1,159 @@ +# spring boot / cloud (十二) 异常统一处理进阶 + +## **前言** + +在[spring boot / cloud (二) 规范响应格式以及统一异常处理](https://my.oschina.net/wangkang80/blog/908919)这篇博客中已经提到了使用@ExceptionHandler来处理各种类型的异常,这种方式也是互联网上广泛的方式 + +今天这篇博客,将介绍一种spring boot官方文档上的统一处理异常的方式.大家可以在[spring boot 官方文档](http://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/htmlsingle/#boot-features-error-handling)查看介绍 + +在开始介绍新的方法之前 , 我们先来分析一下 , 以前的做法有那些地方是需要优化的 + +## **场景分析** + +通常我们需要做统一异常处理的需求,大概都是要规范异常输出,以及处理,通同一套抽象出来的逻辑来处理所有异常. + +但是在当前流行RestFul风格接口的环境下,对异常的输出还做了额外的一个要求,就是针对不同的错误需要输出对应的http状态. + +在前面的实现中,我们大可以指定一个处理Exception的@ExceptionHandler,这样所有异常都能囊括了,但是却无法很好的将http状态区分开来. + +如果要实现不同的异常输出不同的http状态,在原来的做法里就要将每个异常都穷举出来,然后做不同的设定. + +显然,我们是不希望这样做的,显得太不聪明,不过还好,spring已经帮我们把这一步已经做掉了,我们只需处理自己关心的异常即可 + +``` java +@ExceptionHandler(value = 要拦截的异常.class) +@ResponseStatus(响应状态) +@ResponseBody +public RestResponse exception(要拦截的异常 exception) { + return new RestResponse<>(ErrorCode.ERROR, buildError(exception)); +} + +@ExceptionHandler(value = Exception.class) +@ResponseStatus(500) +@ResponseBody +public RestResponse exception(Exception exception) { + return new RestResponse<>(ErrorCode.ERROR, buildError(exception)); +} +``` + +## **源码解读** + +在[官方文档](http://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/htmlsingle/#boot-features-error-handling)中指出,你需要实现一个类,使用@ControllerAdvice标注,然后继承至ResponseEntityExceptionHandler类. + +这个ResponseEntityExceptionHandler类是一个抽象类,如下是它的核心方法 + +``` java +@ExceptionHandler({ + NoSuchRequestHandlingMethodException.class, + HttpRequestMethodNotSupportedException.class, + .....省略 + }) +public final ResponseEntity 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 + +``` java +@ControllerAdvice +@Slf4j +public class ExceptionHandle extends ResponseEntityExceptionHandler { + ..... +} +``` + +实现exception方法,使用@ExceptionHandler拦截Exception,那么在这里,所有的异常都会进入这个方法进行处理. + +然后调用父类的handleException方法(上面提到的),让spring默认的异常处理先处理一遍,如果当前的异常恰巧是被spring拦截的,那么就用spring的默认实现处理,就无需在写额外的代码了,http状态码也一并的会设置好. + +最后在调用我们即将要重写的方法handleExceptionInternal,来处理自定义异常以及规范异常输出 + +``` java +@ExceptionHandler(value = Exception.class) +public ResponseEntity exception(Exception ex, WebRequest request) { + ResponseEntity objectResponseEntity = this.handleException(ex, request); + return this.handleExceptionInternal(ex, null, objectResponseEntity.getHeaders(), objectResponseEntity.getStatusCode(), request); +} +``` + +重写handleExceptionInternal方法, + +在这个方法里面,可以向如下实现一样,去处理项目中自定义的异常,将其规范为想要的输出格式, + +最后再调用父类的handleExceptionInternal方法,将控制权交还给spring, + +这样就完成了整个异常处理的流程 + +``` java +@Override +protected ResponseEntity 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 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错误的方式,敬请期待 + +## **代码仓库** (博客配套代码) + +- [udf-starter : 基础项目,脚手架,框架](https://gitee.com/wangkang/udf) + +- [udf-sample : 集成样例](https://gitee.com/wangkang/udf-sample) + +## **项目主页**(防走丢) + +[http://itkk.org:81/](http://itkk.org:81/) + +--------- + +想获得最快更新,请关注公众号 + +![想获得最快更新,请关注公众号](https://static.oschina.net/uploads/img/201705/24155414_Pukg.jpg "想获得最快更新,请关注公众号") +