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


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

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


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

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

  • 相关阅读:
    第02组 Beta冲刺(1/4)
    第02组 Alpha事后诸葛亮
    第02组 Alpha冲刺(4/4)
    第02组 Alpha冲刺(3/4)
    团队作业6——复审与事后分析(集合贴)
    事后诸葛亮分析报告
    Alpha阶段项目复审
    团队作业5——测试与发布(Alpha版本)
    团队作业四——七天的敏捷冲刺日志集合贴
    第 7 篇 Scrum 冲刺博客
  • 原文地址:https://www.cnblogs.com/itkk/p/7492946.html
Copyright © 2020-2023  润新知