摘要: SpringBoot异常处理。javascript
Fundebug经受权转载,版权归原做者全部。前端
在 Web
开发中, 咱们常常会须要处理各类异常, 这是一件棘手的事情, 对于不少人来讲, 可能对异常处理有如下几个问题:java
try-catch
)异常, 何时须要抛出(throws
)异常到上层.dao
层捕获仍是在 service
捕获, 仍是在 controller
层捕获.既然谈到异常, 咱们先来讲一下异常处理的反例, 也是不少人容易犯的错误, 这里咱们同时讲到前端处理和后端处理 :git
前端代码github
$.ajax({
type: "GET",
url: "/user/add",
dataType: "json",
success: function(data){
alert("添加成功");
}
});
复制代码
后端代码ajax
try {
// do something
} catch (Exception e) {
e.printStackTrace();
}
复制代码
这是见过最多的异常处理方式了, 若是这是一个添加商品的方法, 前台经过 ajax 发送请求到后端, 指望返回 json 信息表示添加结果. 但若是这段代码出现了异常:spring
那么用户看到的场景就是点击了添加按钮, 但没有任何反应(实际上是返回了 500 错误页面, 但这里前端没有监听 error 事件, 只监听了 success 事件. 但即便加上了error: function(data) {alert("添加失败");}
) 又如何呢? 到底由于啥失败了呢, 用户也不得而知.json
后台 e.printStackTrace()
打印在控制台的日志也会在漫漫的日志中被埋没, 极可能会看不到输出的异常. 但这并非最糟的状况, 更糟糕的事情是连 e.printStackTrace()
都没有, catch
块中是空的, 这样后端的控制台中更是什么都看不到了, 这段代码会像一个隐形的炸弹同样一直埋伏在系统中.后端
前端代码springboot
$.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 的状况下, 重定向到某个页面, 这就更乱了. 对于这种不统一的结构处理起来很是麻烦.
既然要进行统一异常处理, 那么确定要有一个规范, 不能乱来. 这个规范包含前端和后端.
对的, 不要在业务代码中进行捕获异常, 即 dao、service、controller 层的因此异常都所有抛出到上层. 这样不会致使业务代码中的一堆 try-catch
会混乱业务代码.
不要使用 Map 来返回结果, Map 不易控制且容易犯错, 应该定义一个 Java 实体类. 来表示统一结果来返回, 如定义实体类:
public class ResultBean<T> {
private int code;
private String message;
private Collection<T> 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 <V> ResultBean<V> success(Collection<V> 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)
, 不须要返回数据, 即调用前者, 须要返回数据, 调用后者. 如:@RequestMapping("/goods/add")
@ResponseBody
public ResultBean<Goods> getAllGoods() {
List<Goods> 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<V> 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: "DELETE",
url: "/goods/delete",
dataType: "json",
success: function(result){
handlerResult(result, deleteDone);
}
});
}
function deleteDone(data) {
alert("删除成功");
}
复制代码
showError
和 handlerResult
是公共方法, 分别用来显示错误和统一处理结果集.
而后将主要精力放在发送请求和处理正确结果的方法上便可, 如这里的 deleteDone 函数, 用来处理操做成功给用户的提示信息, 正所谓各司其职, 前端负责操做成功的消息提示更合理, 而错误信息只有后台知道, 因此须要后台来返回.
说了这么多, 还没讲到后端不在业务层捕获任何异常的事, 既然全部业务层都没有捕获异常, 那么全部的异常都会抛出到 Controller 层, 咱们只须要用 AOP 对 Controller 层的全部方法处理便可.
好在 Spring 为咱们提供了一个注解, 用来统一处理异常:
@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, "系统出现错误, 请联系网站管理员!");
}
}
复制代码
在这里统一配置须要处理的异常, 一样, 对于未知的异常, 必定要及时发现, 并进行处理. 推荐出现未知异常后发送邮件, 提示技术人员.
总结一下统一异常处理的方法:
@ControllerAdvice
来处理.一个简单的演示项目: github.com/zhaojun1998…
本文做者: 赵俊 本文连接: www.zhaojun.im/springboot-… 版权声明: 本博客全部文章除特别声明外,均采用BY-NC-SA许可协议。转载请注明出处!