SpringMVC/Boot统一数据处理最佳理论转载

原创
小哥 3年前 (2022-10-28) 阅读数 6 #大杂烩

前言

Web 开发中, 我们经常需要处理各种异常情况。, 这是一件棘手的事情, 对很多人来说, 异常处理可能存在几个问题:

  • 何时捕获( try-catch )异常, 何时投掷?( throws )上级例外.
  • dao 层捕获或 service 捕获, 还是在 controller 层捕获.
  • 引发异常后的操作. 如何返回页面错误信息.

异常处理反例

既然我们谈论的是异常情况, 让我们从一个异常处理的反例开始。, 这也是许多人容易犯的错误。, 在这里,我们同时讨论前端处理和后端处理。 :

捕获异常后,仅输出到控制台。

前端代码

复制

1 2 3 4 5 6 7 8

$.ajax({ type: "GET", url: "/user/add", dataType: "json", success: function(data){ alert("添加成功"); } });

后端代码

复制

1 2 3 4 5

try { // do something } catch (Exception e) { e.printStackTrace(); }

这是处理异常的最常见方式。, 如果这是一种增加商品的方式。, 前台通过 ajax 向后端发送请求, 期望返回 json 信息表示相加的结果. 但如果此代码中有异常:

  • 然后,用户看到的场景是单击添加按钮, 但没有得到回应(事实上,它是返回的 500 错误页面, 但在这里的前端没有监听。 error 事件, 只监听了 success 事件. 但即使有了附加的 error: function(data) {alert("添加失败");} ) 又如何呢? 你为什么失败了?, 用户也是未知的。.
  • 后台 e.printStackTrace() 控制台上打印的日志也会被埋在长日志中。, 很可能看不到输出异常。. 但这还不是最糟糕的情况。, 更糟糕的是,即使是 e.printStackTrace() 都没有, catch 数据块为空, 这样,后端控制台就什么都看不到了。, 这个代码将像一枚隐形炸弹一样埋伏在系统中。.

混乱的退货方法

前端代码

复制

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

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

后端代码

复制

1 2 3 4 5 6 7 8 9 10 11 12 13 14

@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 中的 flagmessage 此字符串出现在密钥易于处理的时候。, 例如,你在这里的名字是 message , 其他人称它为 msg , 即使有时手在发抖,号码也是错的。, 怎么办? 前台换了 msg 或其他角色?, 前端和后端进行了来回更换。?
更何况,在这种情况下 A 的情况下, 返回 json, 在情况 B 的情况下, 重定向至页面, 这更令人困惑. 处理这种不一致的结构是非常麻烦的。.

异常处理规范

因为它是要进行的 统一 异常处理, 那么一定要有一个规范, 不能乱来. 该规格包括前端和后端。.

不捕捉任何异常

对的, 不要在 在业务代码中 捕获异常, 即 dao、service、controller 层,因此所有异常都被抛出到上层。. 这样不会导致在业务代码中的一堆 try-catch 会把业务代码搞乱.

统一退货结果集

不要使用 Map 返回结果, Map 它不容易控制,也很容易出错。, 应该定义一个 Java 实体类. 表示要返回的统一结果, 例如定义实体类:

复制

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33

public class ResultBean { private int code; private String message; private Collection data;

private ResultBean() {

}

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

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

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

// getter / setter 略

}

  • 正常情况: 调用 ResultBean.success()ResultBean.success(Collection<V> data) , 无需返回任何数据, 也就是说,前者叫做, 需要返回数据, 调用后者. 如:

复制

1 2 3 4 5 6

@RequestMapping("/goods/add") @ResponseBody public ResultBean getAllGoods() { List goods = goodsService.findAll(); return ResultBean.success(goods); }

复制

1 2 3 4 5 6

@RequestMapping("/goods/update") @ResponseBody public ResultBean updateGoods(Goods goods) { goodsService.update(goods); return ResultBean.success(); }

一般情况下,只需要调用查询方法 ResultBean.success(Collection<V> data) 来返回 N 条数据, 其他如删除, 应该调用诸如修改之类的方法。 ResultBean.success() , 即在在业务代码中只处理正确的功能, 不要对异常情况做出任何判断. 你也不需要是对的 update 或 delete 判断更新的次数。(个人建议, 基于业务的实际需求). 只要没有抛出异常, 我们认为用户操作是成功的。. 并在前端处理操作成功的提示信息, 不要回到后台 “行动成功” 等字段.

前台收到的信息是:

复制

1 2 3 4 5 6 7 8 9 10 11 12 13 14

{ "code": 0, "message": "success", "data": [ { "name": "商品1", "price": 50.00, }, { "name": "商品2", "price": 99.99, } ] }

  • 抛出异常: 在引发异常之后, 我们应该打电话给 ResultBean.error(int code, String message) , 返回状态代码和错误消息。, 我们约定 code 为 0 表示操作成功, 12 等于正数表示用户输入错误, -1 , -2 等于负数表示系统错误.

前台收到的信息是:

复制

1 2 3 4 5

{ "code": -1, "message": "XXX 参数有问题。, 请重新填写", "data": null }

前端统一处理:

在返回结果集规范之后, 前端非常容易操作。:

复制

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

/**

  • 显示错误信息
  • @param result: 错误信息 */ function showError(s) { alert(s); }

/**

  • 处理 ajax 请求结果
  • @param result: ajax 返回结果
  • @param fn: 成功的处理功能 ( 传入data: fn(result.data) ) */ function handlerResult(result, fn) { // 操作成功,失败提示原因 if (result.code == 0) { fn(result.data); } // 用户操作异常, 这里可能是对的。 1 或 2 和其他错误代码分别处理。, 也可以 result.code > 0 到粗粒度处理。, 根据业务情况. else if (result.code == 1) { showError(result.message); } // 系统异常, 这里可能是对的。 -1 或 -2 和其他错误代码分别处理。, 也可以 result.code > 0 到粗粒度处理。, 根据业务情况. else if (result.code == -1) { showError(result.message); } // 如果你做出细粒度的州代码判断。, 那么我们应该把重点放在这里没有出现的状态代码上。. 此判断仅建议保留开发阶段用于发现未定义的状态代码。. else { showError("出现未定义的状态代码:" + result.code); } }

/**

  • 根据 id 删除商品 */ function deleteGoods(id) { $.ajax({ type: "GET", url: "/goods/delete", dataType: "json", success: function(result){ handlerResult(result, deleteDone); } }); }

function deleteDone(data) { alert("删除成功"); }

showErrorhandlerResult 是公共方法, 分别用于显示错误和统一处理结果集.

然后专注于发送请求和处理正确结果的方式。, 如这里的 deleteDone 函数, 向用户提示用于处理操作成功的信息, 俗话说,各司其职。, 负责操作成功的前端消息提示更加合理。, 并且该错误消息仅由后台知道。, 所以需要背景才能返回.

后端统一处理异常

说了这么多话, 我还没有谈到后端,没有在业务层捕获任何异常。, 因为所有业务层都没有捕获异常, 则所有异常都将被抛出。 Controller 层, 我们只需要使用 AOP 对 Controller 层的所有方法都被处理。.

好在 Spring 为我们提供了一个注释, 用于统一处理异常。:

复制

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25

@ControllerAdvice @ResponseBody public class WebExceptionHandler {

private static final Logger log = LoggerFactory.getLogger(WebExceptionHandler.class);

@ExceptionHandler
public ResultBean unknownAccount(UnknownAccountException e) {
    log.error("帐户不存在", e);
    return ResultBean.error(1, "帐户不存在");
}

@ExceptionHandler
public ResultBean incorrectCredentials(IncorrectCredentialsException e) {
    log.error("密码错误", e);
    return ResultBean.error(-2, "密码错误");
}

@ExceptionHandler
public ResultBean unknownException(Exception e) {
    log.error("发生未知异常", e);
    // 发送电子邮件通知技术人员.
    return ResultBean.error(-99, "系统错误, 请联系网站管理员!");
}

}

这里,要处理的异常是统一配置的。, 同样, 对于未知异常, 一定要及时找出真相, 和加工. 建议在发生未知异常后发送邮件。, 提示技术人员.

总结

总结了统一异常处理的方法。:

  1. 不要使用各种数据类型的随机返回, 统一返回值规范.
  2. 不在在业务代码中捕获任何异常, 全部交由 @ControllerAdvice 来处理.

一个简单的示范项目: https://github.com/zhaojun1998/exception-handler-demo

版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除