前言:
目前小伙伴们对“spring异常ajax”大约比较着重,看官们都需要分析一些“spring异常ajax”的相关文章。那么小编在网上网罗了一些关于“spring异常ajax””的相关知识,希望同学们能喜欢,兄弟们快快来了解一下吧!前言
在web开发中, 我们经常会需要处理各种异常, 这是一件棘手的事情, 对于很多人来说, 可能对异常处理有以下几个问题:
什么时候需要捕获(try-catch)异常, 什么时候需要抛出(throws)异常到上层。
在dao层捕获还是在service捕获, 还是在controller层捕获。
抛出异常后要怎么处理。 怎么返回给页面错误信息。
既然谈到异常, 我们先来说一下异常处理的反例, 也是很多人容易犯的错误, 这里我们同时讲到前端处理和后端处理 :
前端代码
$.ajax({ type: "GET", url: "/user/add", dataType: "json", success: function(data){ alert("添加成功"); }});
后端代码
try { // do something} catch (Exception e) { e.printStackTrace();}
这是见过最多的异常处理方式了, 如果这是一个添加商品的方法, 前台通过 AJAX 发送请求到后端, 期望返回 JSON 信息表示添加结果。 但如果这段代码出现了异常:
那么用户看到的场景就是点击了添加按钮, 但没有任何反应(其实是返回了 500 错误页面, 但这里前端没有监听 error 事件, 只监听了 success 事件。 但即使加上了 error: function(data) {alert("添加失败");}) 又如何呢? 到底因为啥失败了呢, 用户也不得而知。
后台 e.printStackTrace() 打印在控制台的日志也会在漫漫的日志中被埋没, 很可能会看不到输出的异常。 但这并不是最糟的情况, 更糟糕的事情是连 e.printStackTrace() 都没有,catch 块中是空的, 这样后端的控制台中更是什么都看不到了, 这段代码会像一个隐形的炸弹一样一直埋伏在系统中。
前端代码
$.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")@ResponseBodypublic 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 的情况下, 重定向到某个页面, 这就更乱了。 对于这种不统一的结构处理起来非常麻烦。
既然要进行统一异常处理, 那么肯定要有一个规范, 不能乱来。 这个规范包含前端和后端。
对的, 不要在业务代码中进行捕获异常, 即 dao、service、controller 层的所以异常都全部抛出到上层。 这样不会导致业务代码中的一堆 try-catch 会混乱业务代码。
不要使用 Map 来返回结果, Map 不易控制且容易犯错, 应该定义一个 Java 实体类。 来表示统一结果来返回, 如定义实体类:
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 data), 不需要返回数据, 即调用前者, 需要返回数据, 调用后者。 如:
@RequestMapping("/goods/add")
@ResponseBody
public ResultBean getAllGoods() {
List goods = goodsService.findAll();
return ResultBean.success(goods);
}
@RequestMapping("/goods/update")
@ResponseBody
public ResultBean updateGoods(Goods goods) {
goodsService.update(goods);
return ResultBean.success();
}
一般只有查询方法需要调用 ResultBean.success(Collection data) 来返回 N 条数据, 其他诸如删除, 修改等方法都应该调用 ResultBean.success(), 即在业务代码中只处理正确的功能, 不对异常做任何判断。 也不需要对 update 或 delete 的更新条数做判断(个人建议, 实际需要根据业务). 只要没有抛出异常, 我们就认为用户操作成功了。 且操作成功的提示信息在前端处理, 不要后台返回 “操作成功” 等字段。
前台接受到的信息为:
{ "code": 0, "message": "success", "data": [ { "name": "商品1", "price": 50.00, }, { "name": "商品2", "price": 99.99, } ]}
抛出异常: 抛出异常后, 我们应该调用ResultBean.error(int code, String message), 来将状态码和错误信息返回, 我们约定code为 0 表示操作成功,1或2等正数表示用户输入错误,-1,-2等负数表示系统错误。
前台接受到的信息为:
{ "code": -1, "message": "XXX 参数有问题, 请重新填写", "data": null}
返回的结果集规范后, 前端就很好处理了:
/** * 显示错误信息 * @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("删除成功");}
showError 和 handlerResult 是公共方法, 分别用来显示错误和统一处理结果集。
然后将主要精力放在发送请求和处理正确结果的方法上即可, 如这里的 deleteDone 函数, 用来处理操作成功给用户的提示信息, 正所谓各司其职, 前端负责操作成功的消息提示更合理, 而错误信息只有后台知道, 所以需要后台来返回。
说了这么多, 还没讲到后端不在业务层捕获任何异常的事, 既然所有业务层都没有捕获异常, 那么所有的异常都会抛出到 Controller 层, 我们只需要用 AOP 对 Controller 层的所有方法处理即可。
好在 Spring 为我们提供了一个注解, 用来统一处理异常:
@ControllerAdvice@ResponseBodypublic 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, "系统出现错误, 请联系网站管理员!"); }}
在这里统一配置需要处理的异常, 同样, 对于未知的异常, 一定要及时发现, 并进行处理。 推荐出现未知异常后发送邮件, 提示技术人员。
总结一下统一异常处理的方法:
不使用随意返回各种数据类型, 要统一返回值规范。
不在业务代码中捕获任何异常, 全部交由 @ControllerAdvice 来处理。
标签: #spring异常ajax