从Spring-Boot开始深入理解Spring系列(六)——Spring-Boot统一异常处理的最佳实践

为什么需要统一异常处理?

不用的现状(必要性)

问题代码一:(返回值不统一,混乱)

$.ajax({
    type: "GET",
    url: "/goods/add",
    dataType: "json",
    success: function(data) {
        if (data.flag) {
            alert("添加成功");
        } else {
            alert(data.message);
        }
    },
    error: function(data){
        alert("添加失败");
    }
});

问题代码二:

@RequestMapping("/goods/add")
@ResponseBody
public Map add(Goods goods) {
    Map map = new HashMap();
    try {
        // do something
        map.put(flag, true);
    } catch (Exception e) {
        e.printStackTrace();
        map.put("flag", false);
        map.put("message", e.getMessage());
    }
    reutrn map;
}

这种方式捕获异常后, 返回了错误信息, 且前台做了一定的处理, 看起来很完善? 但用 HashMap 中的 flag 和 message 这种字符串来当键很容易处理, 例如你这里叫 message, 别人起名叫 msg, 甚至有时手抖打错了, 怎么办? 前台再改成 msg 或其他的字符?, 前端后端这样一直来回改?
更有甚者在情况 A 的情况下, 返回 json, 在情况 B 的情况下, 重定向到某个页面, 这就更乱了. 对于这种不统一的结构处理起来非常麻烦.

小结:

  • 一大堆的try catch语句
  • 前端要做各种没有意义的适配
  • 导致前后端严重耦合

使用的好处(意义)

  • 代码更加干净清爽
  • 异常统一由项目经理leader使用AOP来进行处理和定义,不需要每个开发自己定义维护
  • 统一的接口返回值,前后端交互更简单。
  • 充分解耦前后端,面向接口编程(符合契约式编程原则)

如何做统一的异常处理?

原则和约定

  • 不要捕获任何异常
    不要在业务代码中进行捕获异常, 即 dao、service、controller 层的所以异常都全部抛出到上层. 这样不会导致业务代码中的一堆 try-catch 会混乱业务代码.
  • 统一返回结果集
    不要使用 Map 来返回结果, Map 不易控制且容易犯错, 应该定义一个 Java 实体类. 来表示统一结果来返回
  • 前端统一处理提示信息
  • 后端统一处理异常(使用AOP)
    在这里统一配置需要处理的异常, 同样, 对于未知的异常, 一定要及时发现, 并进行处理. 推荐出现未知异常后发送邮件, 提示技术人员.

如何保证新人和代码的维护者保持统一异常处理的好习惯?

岗前培训考核机制

1、新人上手文档手册和文档
2、建立岗前培训以及上岗前的考核机制
3、岗前培训,标准示例代码的学习。工具的学习、配置,最佳实践等。
4、岗前,完成根据题库设定的试题、实战型项目的考核。到此完成验收。

定期codeReview机制

1、工具检查,静态代码扫描工具:sonarqube或者阿里的规约插件等
2、线上使用gitlib加gitflow,进行在线review
3、线下定期举行,meeting式、三俩结组式的codeReview

建立良性的长效激励机制

要让定期review和岗前培训得到长期的支持和落实。
需要高层领导在激励机制上,做出一定的设计和支持。

传帮带机制

主动发现并帮助团队成员,共同进步和成长。定期组织主题技术的分享。
这层,主要是项目leader和项目经理的考核范围,应纳入其绩效考核。

在spring-boot中如何做异常的统一处理?

1、统一在AOP中进行处理(使用@ControllerAdvice,@ExceptionHandler)

package com.dynamic.springbootbvexcetionhandler.exception;

import com.dynamic.springbootbvexcetionhandler.util.ResultBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;


/**
 * a global execption handler use aop
 *
 * @author Administrator
 * @date 2019/1/6
 * commpany: yonyou
 * version:v1.0.0
 */
@ControllerAdvice
@ResponseBody
public class WebExceptionHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(WebExceptionHandler.class);
    @ExceptionHandler
    public ResultBean methodArgumentNotValid(BindException e) {
        LOGGER.error("参数校验失败",e);
        StringBuilder errorMessage = new StringBuilder();
        e.getAllErrors().forEach(objectError ->
                errorMessage.append(objectError.getDefaultMessage()).append(",")
        );
        return ResultBean.error(1, errorMessage.toString());
    }

    @ExceptionHandler
    public ResultBean methodArgumentNotValidForJsonArgumentResolver(MethodArgumentNotValidException e) {
        LOGGER.error("json参数绑定到对象失败",e);
        StringBuilder errorMessage = new StringBuilder();
        e.getBindingResult().getAllErrors().forEach( errors-> errorMessage.append(errors.getDefaultMessage()).append(",") );
        return ResultBean.error(1, errorMessage.toString());
    }

    @ExceptionHandler
    public ResultBean unKnowException(Exception e) {
        LOGGER.error("未知异常", e);
        //发送邮件,或者短信通知
        return ResultBean.error(-999, "发生了未知异常,请联系系统管理员");
    }

    @ExceptionHandler
    public ResultBean ageDeleteException(AgeDeleteException e) {
        LOGGER.error("不能删除18岁以下的用户",e);
        return ResultBean.error(-1, "不能删除18岁以下的用户");
    }
}

2、定义统一的返回对象

private int code;
    private String message;
    private Collection<T> data;

    private ResultBean() {

    }
    private ResultBean(int code, String message) {
        this.code = code;
        this.message = message;
    }
    private ResultBean(int code, String message, Collection<T> data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }



    public int getCode() {
        return code;
    }

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

    public String getMessage() {
        return message;
    }

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

    public Collection<T> getData() {
        return data;
    }

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

    public static   ResultBean error(int code, String message) {
        ResultBean resultBean = new ResultBean(code, message);
        return resultBean;
    }

    public static ResultBean success() {
        ResultBean resultBean = new ResultBean();
        resultBean.setCode(0);
        resultBean.setMessage("success");
        return resultBean;
    }

    public static <V> ResultBean<V> success(Collection<V> data) {
        ResultBean resultBean = new ResultBean();
        resultBean.setMessage("success");
        resultBean.setData(data);
        resultBean.setCode(0);
        return resultBean;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("com.dynamic.springbootbvexcetionhandler.util.ResultBean{");
        sb.append("code=").append(code);
        sb.append(", message='").append(message).append('\'');
        sb.append(", data=").append(data);
        sb.append('}');
        return sb.toString();
    }

源码下载:https://github.com/bill4j/spring-boot-course/tree/develop/spring-boot-exceptionHandler

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值