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

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

前言

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

今天这篇博客,将介绍一种spring boot官方文档上的统一处理异常的方式.你们能够在spring boot 官方文档查看介绍java

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

场景分析

一般咱们须要作统一异常处理的需求,大概都是要规范异常输出,以及处理,通同一套抽象出来的逻辑来处理全部异常.spring

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

在前面的实现中,咱们大能够指定一个处理Exception的@ExceptionHandler,这样全部异常都能囊括了,可是却没法很好的将http状态区分开来.框架

若是要实现不一样的异常输出不一样的http状态,在原来的作法里就要将每一个异常都穷举出来,而后作不一样的设定.ide

显然,咱们是不但愿这样作的,显得太不聪明,不过还好,spring已经帮咱们把这一步已经作掉了,咱们只需处理本身关心的异常便可spring-boot

@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类是一个抽象类,以下是它的核心方法ui

@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错误的方式,敬请期待

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


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

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

相关文章
相关标签/搜索