前言

在项目的开发中,不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的、不可预知的异常需要处理。每个过程都单独处理异常,系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大。 那么,能不能将所有类型的异常处理从各处理过程解耦出来,这样既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护?答案是肯定的。

一、自定义异常

1.用@ControllerAdvice+@ExceptionHandler实现全局异常处理

通常在Controller层需要去捕获service层的异常,防止返回一些不友好的错误信息到客户端,但如果Controller层每个方法都用模块化的try-catch代码去捕获异常,会很难看也难维护。

异常处理最好是解耦的,并且都放在一个地方集中管理。Spring能够较好的处理这种问题

不多说,直接上干货:

ExceptionHander:异常处理类,拦截全局异常并处理

/**
 * @program: com.exception
 * @description: 全局处理异常
 * @author: WangZhiJun
 * * @create: 2019-07-24 16:16
 **/
@ControllerAdvice
class ExceptionHander {
    private static final Logger logger = LoggerFactory.getLogger(ExceptionHander.class);
 
    @ExceptionHandler(value = Exception.class)
    public ResponseEntity handleException(HttpServletRequest request, Exception e) {
        logger.error(dumpThrowable(e));
        ResultBean bean = new ResultBean();
        //自定义国际化异常,根据需要添加相关业务
        if (e instanceof I18nException) {
            final I18nException exception = (I18nException) e;
            bean.setCode(exception.getErrorCode());
            bean.setError(exception.getI18nMessage().toString());
        } else if(e instanceof MissingServletRequestParameterException) {
            //请求参数缺失异常
            MissingServletRequestParameterException missingParameterException = (MissingServletRequestParameterException) e;
            bean.setError(missingParameterException.getParameterName() + " is null");
        } else if (e instanceof SQLException || e instanceof DuplicateKeyException) {
            //SQL相关异常
            bean.setError(ErrorCode.SQL.SQL_ERROR);
        } else if (e instanceof MyBatisSystemException || e instanceof UncategorizedSQLException) {
            //Mybatis或者JDBC异常
            bean.setError(ErrorCode.SQL.MYBATIS_ERROR);
        } else {
            //等等等异常,根据需要添加
            bean.setCode(ErrorCode.ERROR);
            bean.setError(e);
        }
        return new ResponseEntity(bean, HttpStatus.OK);
    }
 
    //将信息打印在控制台
    private String dumpThrowable(Throwable t) {
        if (t == null) {
            return null;
        }
        try {
            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            t.printStackTrace(pw);
            sw.close();
            pw.close();
            return "\r\n" + sw.toString() + "\r\n";
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "内部错误:" + t.getMessage();
    }
}

I18nException :自定义国际化异常

/**
 * @program: com.exception
 * @description: 全局异常
 * @author: WangZhiJun
 * * @create: 2019-07-24 16:16
 **/
public class I18nException extends Exception {
 
    private final ErrorCode.ErrorCodeMessage error;
    private final int code;
    private final I18nMessage message;
 
    public I18nException(ErrorCode.ErrorCodeMessage message) {
        super(message.getMessage().toString());
        this.error = message;
        this.message = message.getMessage();
        this.code = message.getCode();
    }
 
    public ErrorCode.ErrorCodeMessage getErrorCodeMessage() {
        return error;
    }
    public I18nMessage getI18nMessage() {
        return message;
    }
    public int getErrorCode() {
        return code;
    }
}

ErrorCode :异常信息的返回码,可根据需要添加不同的异常

/**
 * @program: com.exception
 * @description: 异常码
 * @author: WangZhiJun
 * * @create: 2019-07-24 16:16
 **/
public class ErrorCode {
 
    public static final int ERROR = 0x0000;
    public static final int SUCCESS = 0x0001;
 
    public static final int ERROR_PARAMETER = 0x0011;
 
 
    public interface ErrorCodeMessage extends Serializable {
        /**
         * 获取错误码
         * @return int
         */
        int getCode();
 
        /**
         * 获取错误消息
         * @return I18nMessage
         */
        I18nMessage getMessage();
    }
 
    /**
     *  SQL相关异常
     */
    public enum SQL implements ErrorCodeMessage {
 
        .......
    }
 
    /**
     *  文件相关异常
     */
    public enum File implements ErrorCodeMessage {
 
        .......
    }
 
    /**
     *  权限相关异常
     */
    public enum Authentication implements ErrorCodeMessage {
 
        /**
         * 没有权限
         */
        PERMISSION_DENIED(0x1101, I18nMessage.PERMISSION_DENIED);
 
 
        private final int code;
        private final I18nMessage message;
 
        Authentication(int code, I18nMessage message) {
            this.code = code;
            this.message = message;
        }
 
        @Override
        public int getCode() {
            return code;
        }
 
        @Override
        public I18nMessage getMessage() {
            return message;
        }
 
        public Authentication setMessage(String fmt, Object... args) {
            if (message != null) {
                message.setMessage(fmt, args);
            }
            return this;
        }
 
        @Override
        public String toString() {
            return String.valueOf(message);
        }
    }
}

I18nMessage :异常的返回信息,可以根据需要进行补充,比如将异常信息映射到xml文件中等

/**
 * @program: com.exception
 * @description: 异常信息
 * @author: WangZhiJun
 * * @create: 2019-07-24 16:16
 **/
public enum I18nMessage implements Serializable {
 
    /**
     * 异常信息
     */
    FILE_NOT_FOUND("file_not_found"),
 
    /**
     * 权限相关异常
     */
    PERMISSION_DENIED("permission_denied"),
 
    /**
     * 数据库相关异常
     */
    SQL_ERROR("sql_error"),
    MYBATIS_ERROR("mybatis_error");
 
    // 校验
    VALID_ERROR("valid_error");
 
    /**
     * ****************************   方法 ******************************************
     */
    private final String key;
    private String message;
 
    I18nMessage(String key) {
        this.key = key;
    }
 
    public I18nMessage setMessage(String fmt, Object... obj) {
        try {
            this.message = String.format(fmt, obj);
        } catch (Exception e) {
        }
        return this;
    }
}

结果

当在业务层某处要抛出此异常时控制到会打印错误信息

20190724180645292.png

界面提示信息也会比较友好而不会直接提示原生错误信息

20190724180734324.png

二、数据校验异常处理

用@RequestBody+@Valid进行数据绑定和数据校验。

下面简单演示添加一个用户时对用户数据进行验证,无需在业务层进行单独频繁的验证

UserBean:先看下实体类中进行了哪些校验

/**
 * @program: com.exception
 * @description: 用户bean
 * @author: WangZhiJun
 * * @create: 2019-07-24 18:10
 **/
@Data
public class UserBean {
 
    @NotNull(message = "userId_not_null", groups = {Add.class})
    private String userId;
 
    @NotBlank(message = "password_length_limit", groups = {Add.class})
    private String username;
 
    /**
     * 密码校验长度大于零,且在标有Add.class的参数上
     */
    @NotBlank(message = "password_length_limit", groups = {Add.class})
    private String password;
 
    /**
     *  年龄限制在最少15岁
     */
    @Min(value = 15,message = "age_limit", groups = {Add.class})
    private Integer age;
 
    /**
     * 性别只能填男或者女(正则)
     */
    @Pattern(regexp = "[男|女]", message = "sex_is_boy_or_girl", groups = {Add.class})
    private String sex;
}

20190725094640357.png

通过@RequestBody将请求body中的json与对象(UserBean)绑定,使用@Valid进行对象数据验证。如果不使用@Valid,UserBean中的@NotNull等注解不生效。

20190725134700698.png

而在对象数据中每个属性上都有group值,@Valid中也有这个值,这样的话就可以随意控制数据数据校验的位置

20190725105058426.png

20190725105130380.png

当使用上述数据进行接口调用时,由于性别正则校验失败,会抛出异常信息

2019072510540516.png

数据校验失败时,Spring框架会抛出 MethodArgumentNotValidException 异常,利用第一大点提到的全局异常捕获,在ExceptionHander加入校验异常捕获的方法即可对校验异常进行处理

    /**
     * 用于处理校验失败的异常
     */
    @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class, ConstraintViolationException.class})
    public ResponseEntity<ResultBean> bindException(Exception e) {
        StringBuilder errorMessage = new StringBuilder(I18nMessage.VALID_ERROR.toString()).append(":");
        String message;
        if (e instanceof ConstraintViolationException) {
            ConstraintViolationException ce = (ConstraintViolationException) e;
            for (ConstraintViolation<?> messageInfo : ce.getConstraintViolations()) {
                message = messageInfo.getMessage();
                errorMessage.append(StringUtils.isEmpty(message) ? messageInfo.getMessage() : message).append(", ");
            }
        } else {
            BindingResult bindingResult;
            if (e instanceof MethodArgumentNotValidException) {
                MethodArgumentNotValidException methodArgumentNotValidException = (MethodArgumentNotValidException) e;
                bindingResult = methodArgumentNotValidException.getBindingResult();
            } else {
                BindException bindException = (BindException) e;
                bindingResult = bindException.getBindingResult();
            }
            for (FieldError fieldError : bindingResult.getFieldErrors()) {
                message = fieldError.getDefaultMessage();
                errorMessage.append(StringUtils.isEmpty(message) ? fieldError.getDefaultMessage() : message).append(", ");
            }
        }
        errorMessage.deleteCharAt(errorMessage.lastIndexOf(","));
        return new ResponseEntity<>(new ResultBean().setError(errorMessage.toString().trim()), HttpStatus.OK);
    }

此时再次调用:

2019072511044073.png

最后附上封装好的数据响应类ResultBean

/**
 * @program: com.exception
 * @description: Http 统一数据响应结果
 * @author: WangZhiJun
 * * @create: 2019-07-24 16:16
 **/
@Data
public class ResultBean<T> implements Serializable {
 
    private static final long serialVersionUID = 0;
 
 
    /**
     * 错误码(可用于条件判断)
     */
    private int code = ErrorCode.ERROR;
 
    /**
     * 提示信息(不要用于条件判断)
     */
    private String message;
 
    /**
     * 数据
     */
    private T data;
 
    public int getCode() {
        return code;
    }
 
    public ResultBean<T> setCode(int code) {
        this.code = code;
        return this;
    }
 
    public String getMessage() {
        return message;
    }
 
 
    public ResultBean<T> setMessage(I18nMessage message) {
        this.message = message == null ? null : message.toString();
        return this;
    }
 
 
    public T getData() {
        return data;
    }
 
    public ResultBean<T> setData(T data) {
        this.data = data;
        return this;
    }
 
 
    public ResultBean<T> setSuccess() {
        setCode(ErrorCode.SUCCESS);
        return this;
    }
 
    public ResultBean<T> setError(ErrorCode.ErrorCodeMessage message) {
        setCode(message.getCode());
        setMessage(message.getMessage());
        return this;
    }
 
    public ResultBean<T> setError(Throwable throwable) {
        String message = throwable != null ? throwable.getMessage() : null;
        return setError(StringUtils.isEmpty(message) && throwable != null ? throwable.getClass().getSimpleName() : message);
    }
 
    public ResultBean<T> setError(String message) {
        setCode(ErrorCode.ERROR);
        this.message = message;
        return this;
    }
}

大家可以根据自己公司封装的数据响应类对其他类进行一定的修改。

是不是觉得很方便,这样在写业务逻辑的时候,就不需要烦恼要返回不同的异常处理结果,随时随地可以抛出自己想要抛出的异常,只需要Errcode.java和I18nMessage中加入相应的异常码和异常信息即可!

看到这里的话点个赞关注我给我动力哦


版权声明:文章转载请注明来源,如有侵权请联系博主删除!
最后修改:2019 年 12 月 25 日 11 : 54 AM
如果觉得我的文章对你有用,请随意赞赏