Java 项目中异常的统一处理

1. 前言

Java中的异常处理,常用的处理方式,直接throw 一个异常对象,或者捕获catch一个异常,让程序继续执行,但是在一个项目中,这种简单处理异常的方式并不可取,因为在业务代码中直接抛出或者捕获异常,跟业务的耦合性都太高,而且代码冗余不利于维护;更好的处理方式,应该是利用切面编程的思想,自定义一个全局异常类,返回一个统一规范的异常信息;

2. 对Java中异常的理解

首先我们先理解一下Java中的异常体系;

Java 项目中异常的统一处理_第1张图片

如上图所示,Java中的异常的 主要分为Error和Exception两大类,都继承自Throwable,

1) Exception

是程序正常运行中,可以预料的意外情况,可以被捕获,进行相应的处理;Exception又分为checked Exception和unchecked Exception,可检查的异常在源代码里必须显示地进行捕获处理,这是编译期检查的一部分;如IOException;不检查的异常就是运行时异常,类似于NullPointException,ArrayIndexOutOfBoundsException

2) Error

Error是指在正常情况下,不大可能出现的情况,绝大部分的Error都会导致程序(如JVM)处于非正常的,不可恢复状态,如OutOfMemoryError之类的,都是Error的子类

3. 利用@ControllerAdvice与@ExceptionHandler注解实现异常的全局处理

@ControllerAdvice是在类上声明的注解,与其他注解组合,实现不同的定制功能,其用法主要有三点:

  • @ExceptionHandler注解标注的方法:用于捕获Controller中抛出的不同类型的异常,从而达到异常全局处理的目的;
  • @InitBinder注解标注的方法:用于请求中注册自定义参数的解析,从而达到自定义请求参数格式的目的;
  • @ModelAttribute注解标注的方法:表示此方法会在执行目标Controller方法之前执行 

在项目中实现异常全局处理的过程如下: 

  • 自定义一个全局异常处理类,该类中并使用@ControllerAdvice修饰

  • 使用@ExceptionHandler注解捕获指定或自定义的异常;

  • 使用@ControllerAdvice集成@ExceptionHandler的方法到一个类中;

  • 必须定义一个通用的异常捕获方法,便于捕获未定义的异常信息;

  • 自定一个异常类,捕获针对项目或业务的异常;

  • 异常的对象信息补充到统一结果枚举中;

主要的实现代码:

1) 通用异常结果集的封装

package com.zach.exception.common.result;

/**
 * @Classname ResponResult
 * @Description:
 * @Date 2020/5/2 17:23
 * @Created by Zach
 */
public interface ResponseResult {

}





import java.util.Date;

/**
 * @Classname DefalultErrorResult
 * @Description:
 * @Date 2020/5/2 17:24
 * @Created by Zach
 */
public class DefaultErrorResult implements ResponseResult {
    /**
     * HTTP响应状态码 {@link HttpStatus}
     */
    private Integer status;

    /**
     * HTTP响应状态码的英文提示
     */
    private String error;

    /**
     * 异常堆栈的精简信息
     *
     */
    private String message;

    /**
     * 我们系统内部自定义的返回值编码,{@link ResultCode} 它是对错误更加详细的编码
     *
     * 备注:spring boot默认返回异常时,该字段为null
     */
    private Integer code;

    /**
     * 调用接口路径
     */
    private String path;

    /**
     * 异常的名字
     */
    private String exception;

    /**
     * 异常的错误传递的数据
     */
    private Object errors;

    /**
     * 时间戳
     */
    private Date timestamp;

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    public String getError() {
        return error;
    }

    public void setError(String error) {
        this.error = error;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public String getException() {
        return exception;
    }

    public void setException(String exception) {
        this.exception = exception;
    }

    public Object getErrors() {
        return errors;
    }

    public void setErrors(Object errors) {
        this.errors = errors;
    }

    public Date getTimestamp() {
        return timestamp;
    }

    public void setTimestamp(Date timestamp) {
        this.timestamp = timestamp;
    }

    public static DefaultErrorResult failure(ResultCode resultCode, Throwable e, HttpStatus httpStatus, Object errors) {
        DefaultErrorResult result = DefaultErrorResult.failure(resultCode, e, httpStatus);
        result.setErrors(errors);
        return result;
    }

    public static DefaultErrorResult failure(ResultCode resultCode, Throwable e, HttpStatus httpStatus) {
        DefaultErrorResult result = new DefaultErrorResult();
        result.setCode(resultCode.getCode());
        result.setMessage(resultCode.getMessage());
        result.setStatus(httpStatus.value());
        result.setError(httpStatus.getReasonPhrase());
        result.setException(e.getClass().getName());
        String path = RequestContextUtils.getRequest().getRequestURI();
        result.setPath(path);
        result.setTimestamp(new Date());
        return result;
    }

    public static DefaultErrorResult failure(BusinessException e) {
        BusinessExceptionEnum ee = BusinessExceptionEnum.getByEClass(e.getClass());
        if (ee != null) {
            return DefaultErrorResult.failure(ee.getResultCode(), e, ee.getHttpStatus(), e.getData());
        }

        DefaultErrorResult defaultErrorResult = DefaultErrorResult.failure(e.getResultCode() == null ? ResultCode.SUCCESS : e.getResultCode(), e, HttpStatus.OK, e.getData());
        if (!StringUtils.isEmpty(e.getMessage())) {
            defaultErrorResult.setMessage(e.getMessage());
        }
        return defaultErrorResult;
    }
}




/**
 * @Classname CommonResult
 * @Description:
 * @Date 2020/5/2 17:35
 * @Created by Zach
 */
public class CommonResult implements ResponseResult {
    private Integer code;

    private String msg;

    private T data;

    public CommonResult() {
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public CommonResult(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
    public static CommonResult success() {
        CommonResult result = new CommonResult();
        result.setResultCode(ResultCode.SUCCESS);
        return result;
    }

    public static  CommonResult success(T data) {
        CommonResult result = new CommonResult();
        result.setResultCode(ResultCode.SUCCESS);
        result.setData(data);
        return result;
    }

    public static CommonResult failure(ResultCode resultCode) {
        CommonResult result = new CommonResult();
        result.setResultCode(resultCode);
        return result;
    }

    public static  CommonResult failure(ResultCode resultCode, T data) {
        CommonResult result = new CommonResult();
        result.setResultCode(resultCode);
        result.setData(data);
        return result;
    }

    public static CommonResult failure(String message) {
        CommonResult result = new CommonResult();
        result.setCode(ResultCode.PARAM_IS_INVALID.getCode());
        result.setMsg(message);
        return result;
    }

    private void setResultCode(ResultCode code) {
        this.code = code.getCode();
        this.msg = code.getMessage();
    }
}

2)  全局异常抽象处理类

/**
 * @Classname BaseGlobExceptionAdvice
 * @Description:   全局异常抽象类
 * @Date 2020/5/2 17:01
 * @Created by Zach
 */
public abstract class BaseGlobalExceptionAdvice {
    protected final Logger logger = LoggerFactory.getLogger(getClass());
    /**
     * 违反约束异常
     */
    protected DefaultErrorResult handleConstraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
        logger.info("handleConstraintViolationException start, uri:{}, caused by: ", request.getRequestURI(), e);
        List parameterInvalidItemList = ParameterInvalidItemHelper.convertCVSetToParameterInvalidItemList(e.getConstraintViolations());
        return DefaultErrorResult.failure(ResultCode.PARAM_IS_INVALID, e, HttpStatus.BAD_REQUEST, parameterInvalidItemList);
    }
    /**
     * 处理验证参数封装错误时异常
     */
    protected DefaultErrorResult handleConstraintViolationException(HttpMessageNotReadableException e, HttpServletRequest request) {
        logger.info("handleConstraintViolationException start, uri:{}, caused by: ", request.getRequestURI(), e);
        return DefaultErrorResult.failure(ResultCode.PARAM_IS_INVALID, e, HttpStatus.BAD_REQUEST);
    }

    /**
     * 处理参数绑定时异常(反400错误码)
     */
    protected DefaultErrorResult handleBindException(BindException e, HttpServletRequest request) {
        logger.info("handleBindException start, uri:{}, caused by: ", request.getRequestURI(), e);
        List parameterInvalidItemList = ParameterInvalidItemHelper.convertBindingResultToMapParameterInvalidItemList(e.getBindingResult());
        return DefaultErrorResult.failure(ResultCode.PARAM_IS_INVALID, e, HttpStatus.BAD_REQUEST, parameterInvalidItemList);
    }

    /**
     * 处理使用@Validated注解时,参数验证错误异常(反400错误码)
     */
    protected DefaultErrorResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
        logger.info("handleMethodArgumentNotValidException start, uri:{}, caused by: ", request.getRequestURI(), e);
        List parameterInvalidItemList = ParameterInvalidItemHelper.convertBindingResultToMapParameterInvalidItemList(e.getBindingResult());
        return DefaultErrorResult.failure(ResultCode.PARAM_IS_INVALID, e, HttpStatus.BAD_REQUEST, parameterInvalidItemList);
    }

    /**
     * 处理使用@Validated注解时,接口请求类型不支持(反400错误码)
     */
    protected DefaultErrorResult handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
        logger.info("handleHttpRequestMethodNotSupportedException start, uri:{}, caused by: ", request.getRequestURI(), e);
        return DefaultErrorResult.failure(ResultCode.INTERFACE_METHOD_NOT_SUPPORT, e, HttpStatus.BAD_REQUEST);
    }

    /**
     * 处理通用自定义业务异常
     */
    protected ResponseEntity handleBusinessException(BusinessException e, HttpServletRequest request) {
        logger.info("handleBusinessException start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(), e.getClass(), e.getMessage());
        DefaultErrorResult defaultErrorResult = DefaultErrorResult.failure(e);
        return ResponseEntity
                .status(HttpStatus.valueOf(defaultErrorResult.getStatus()))
                .body(defaultErrorResult);
    }

    /**
     * 处理运行时系统异常(反500错误码)
     */
    protected DefaultErrorResult handleRuntimeException(RuntimeException e, HttpServletRequest request) {
        logger.error("handleRuntimeException start, uri:{}, caused by: ", request.getRequestURI(), e);
        return DefaultErrorResult.failure(ResultCode.SYSTEM_INNER_ERROR, e, HttpStatus.INTERNAL_SERVER_ERROR);
    }

    /**
     * 处理系统异常(反500错误码)
     */
    protected DefaultErrorResult handleException(Exception e, HttpServletRequest request) {
        logger.error("handleRuntimeException start, uri:{}, caused by: ", request.getRequestURI(), e);
        return DefaultErrorResult.failure(ResultCode.SYSTEM_INNER_ERROR, e, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

3) 具体实现

/**
 * @Classname GlobalExceptionAdviceAdvice
 * @Description:
 * @Date 2020/5/2 17:44
 * @Created by Zach
 */
@RestControllerAdvice
public class GlobalExceptionAdviceAdvice extends BaseGlobalExceptionAdvice {

    /**
     * @Description TODO
     * @date 2020/5/2 17:48
     * @auther Zach
     */
    @Override
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(ConstraintViolationException.class)
    public DefaultErrorResult handleConstraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
        return super.handleConstraintViolationException(e, request);
    }
    @Override
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public DefaultErrorResult handleConstraintViolationException(HttpMessageNotReadableException e, HttpServletRequest request) {
        return super.handleConstraintViolationException(e, request);
    }

    @Override
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BindException.class)
    public DefaultErrorResult handleBindException(BindException e, HttpServletRequest request) {
        return super.handleBindException(e, request);
    }

    @Override
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public DefaultErrorResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
        return super.handleMethodArgumentNotValidException(e, request);
    }

    @Override
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public DefaultErrorResult handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
        return super.handleHttpRequestMethodNotSupportedException(e, request);
    }

    /**
     * 处理自定义异常
     */
    @Override
    @ExceptionHandler(BusinessException.class)
    public ResponseEntity handleBusinessException(BusinessException e, HttpServletRequest request) {
        return super.handleBusinessException(e, request);
    }

    /**
     * 处理运行时异常
     */
    @Override
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(RuntimeException.class)
    public DefaultErrorResult handleRuntimeException(RuntimeException e, HttpServletRequest request) {
        return super.handleRuntimeException(e, request);
    }

    /**
     * 处理异常
     */
    @Override
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    public DefaultErrorResult handleException(Exception e, HttpServletRequest request) {
        return super.handleException(e, request);
    }

}

4) 统一结果集的返回

/**
 * @Classname ResponseResultAdvice
 * @Description:
 * @Date 2020/5/2 18:09
 * @Created by Zach
 */
@ControllerAdvice
public class ResponseResultAdvice implements ResponseBodyAdvice {

    @Override
    public boolean supports(MethodParameter methodParameter, Class aClass) {
//通过自定义注解IgnoreResponseAdvice的方式,来决定是否需要统一的结果集处理,留一个注解开关,更加灵活变通
        if (methodParameter.getDeclaringClass().isAnnotationPresent(IgnoreResponseAdvice.class)) {
            return false;
        }
        if (methodParameter.getMethod().isAnnotationPresent(IgnoreResponseAdvice.class)) {
            return false;
        }
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object object, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        if (object instanceof CommonResult)
            return object;
        if (object instanceof DefaultErrorResult) {
            DefaultErrorResult defaultErrorResult = DefaultErrorResult.class.cast(object);
            return new CommonResult(defaultErrorResult.getCode(),defaultErrorResult.getMessage(),defaultErrorResult.getErrors());
        }
        return CommonResult.success(object);
    }
}

 

4. 总结

以上就是主要的实现过程,自定义了生产环境中各种可能出现的异常情况,详细代码请移至: https://github.com/Zach-Zhang/exception-customize

你可能感兴趣的:(学习笔记,技术分享)