锐智互动软件开发公司
400-1050-360
软件开发 APP开发 微信/小程序开发 大型电商平台开发 数据挖掘
400-1050-360
软件开发 APP开发 微信/小程序开发 大型电商平台开发 数据挖掘

软件开发公司 > 动态 > 行业资讯

Spring Boot统一异常处理最佳实践-软件开发 APP开发 软件开发公司 APP开发公司

行业资讯 - 2019 - 04 - 19 Spring 软件开发 异常处理


前言

在 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")
@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

  • 正常情况: 调用 ResultBean.success() 或 ResultBean.success(Collection

@RequestMapping("/goods/add")
@ResponseBody
public ResultBean

一般只有查询方法需要调用 ResultBean.success(Collection

前台接受到的信息为:

{
"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 来处理.

下一章:北京软件开发公司推荐关于后端架构师技术图谱!锐智互动
关于锐智互动

锐智互动网络科技遵循严格的质量和安全标准, 实施严密的安全措施, 拥有成熟可靠的管理和开发流程, 公司凭借多年的行业积累、深厚的 行业专长和成熟的行业实践,为客户持续创造关键价值。我们始终关 注前沿技术,保持国际领先的眼界和技术储备。公司自 成立以来, 在团队成员的共同努力下,已经成功服务于上百家企业,其中包括 我爱我家、联东集团、优财CMA、5100、奔驰、华为、伊利、宝马、 迪思公关、航天国旅、HOTWIND、北京电通等众多知名企业。

咨询热线:400-1050-360

推荐阅读

北京软件外包公司排名第一名有哪些 Tag: app商城软件好开发吗?现在市场的需求是多还是少? Tag: app商城软件好开发 公司功能开发小程序开发的关键是什么? Tag: 开发小程序开发 有什么值得推荐的app开发公司? Tag: app开发公司 如何评估APP软件定制的价格合理性? Tag: APP软件定制 定制化APP软件开发服务,提升企业竞争力 Tag: 定制化APP 软件开发外包的选择标准和方法有哪些? Tag: 软件开发外包 App定制开发的商业模式有哪些?如何评估投资回报? Tag: App定制开发 软件开发定制过程中,如何确保项目质量和进度控制? Tag: 如何确保项目质量和进度控制?

提交需求,获取工期与报价

立即咨询