在 Web 开发中, 我们经常会需要处理各种异常, 这是一件棘手的事情, 对于很多人来说, 可能对异常处理有以下几个问题:
前端代码
$.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;
}
我们在有的项目中会发现try catch的代码很多,看起来很头疼啊。
Java把异常当做对象来处理。Throwable是所有错误或异常的超类。Throwable类有两个直接子类:Error类和Exception类。
1、Error是指java运行时系统的内部错误和资源耗尽错误,是程序无法处理的异常,应用程序不会抛出该类对象。
2、Exception是程序本身可以处理的异常,应尽可能去处理这些异常。Exception分两类,一个是运行时异常RuntimeException,一个是检查异常CheckedException。
3、CheckedException一般是外部错误,这种异常都发生在编译阶段,Java编译器会强制程序去捕获此类异常。
4、RuntimeException是那些可能在Java 虚拟机正常运行期间抛出的异常的超类。这种错误是由程序员引起的错误,可以修正代码解决。
不要在业务代码中进行捕获异常, 即 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 略
}
前端代码
{
"code": 0,
"message": "success",
"data": [
{
"name": "商品1",
"price": 50.00,
},
{
"name": "商品2",
"price": 99.99,
}
]
}
/**
* 显示错误信息
* @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("删除成功");
}
用@ControllerAdvice+@ExceptionHandler实现全局异常处理
通常在Controller层需要去捕获service层的异常,防止返回一些不友好的错误信息到客户端,但如果Controller层每个方法都用模块化的try-catch代码去捕获异常,会很难看也难维护。
异常处理最好是解耦的,并且都放在一个地方集中管理。Spring能够较好的处理这种问题,核心如下,这里主要关注前两个:
单使用@ExceptionHandler,只能在当前Controller中处理异常,与@ControllerAdvice组合使用,则可以实现全局异常处理,不用每个controller都配置。
@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, "系统出现错误, 请联系网站管理员!");
}
}
在项目中我们将异常处理从业务代码中解耦出来,实现异常信息的统一处理和维护,减少了模板代码,改善了代码的风格。
感谢您的阅读~~