程序异常处理总结

描述

平时我们在开发程序的过程中,总是必不可免的会出现各种各样的异常情况,这些异常信息可能来自于业务层也可能由底层代码抛出,如数据访问层等.异常信息如果直接抛出给客户端,会带来很不好的用户体验,所以必须由服务端来做处理,最简单的方式就是直接try catch,但是一个项目里面如果大量的做这种处理,会使得整个系统的代码风格看起来很乱,工作量大且耦合度高,所以在框架层面要做统一的拦截处理.

实现

异常可简单区分为unchecked异常和checked异常,在Java体系里所有的error和runtimeException及其子类都属于unchecked异常,除此之外异常都属于checked异常.
对于checked异常是必须要由程序做try catch处理或者在方法声明处往外throws到调用方,也可直接丢给JVM处理.
对于unchecked异常,系统默认会自动把该异常往上抛出,调用端可做处理也可不做,或者直接丢给JVM.
再来看看Spring框架是如何处理异常的,它提供了三种统一异常处理的方式:
(1)使用SpringMVC提供的简单异常处理器SimpleMappingExceptionResolver.
(2)自定义一个异常处理器,实现Spring提供的异常处理接口 HandlerExceptionResolver或继承自Spring提供的异常处理抽象类AbstractHandlerExceptionResolver.
(3)最简单的方式在Controller层直接使用@ExceptionHandler注解.
一般情况下在项目中我们会自定义各种各样的异常类如业务异常类BusinessException,参数校验异常类ParameterException等等,都继承自java.lang.RuntimeException.在这里我们使用了@ExceptionHandler注解的方式并且在所有controller层的基类BaseController里面写个方法做统一处理后再根据各个调用端的不同包装为相应的实体对象返回过去,这样客户端可以做出友好的提示例如:

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
@ExceptionHandler({Exception.class, BusinessException.class,
UnauthorizedException.class, AuthorizationException.class,
SQLException.class})
@ResponseBody
public Object handlerException(Exception ex, HttpServletRequest request) {
if (StringHelpUtils.isNotEmpty(request.getHeader("device"))
&& (request.getHeader("device").equals("APP") || request
.getHeader("device").equals("H5"))) {
logger.error("客户端设备:{}", request.getHeader("device"));
if (ex instanceof BusinessException) {// 业务异常
return new ResponseEntity().isOk(HttpStatus.FAIL, ex.getMessage(), ex.getMessage());
} else if (ex instanceof UnauthorizedException) {// shiro授权异常
return new ResponseEntity().isOk(HttpStatus.UNAUTHORIZED, "");
} else {// 其他异常
return new ResponseEntity().isOk(HttpStatus.FAIL, "服务器歇菜了。。。", "");
}
} else {
if (ex instanceof BusinessException) {// 业务异常
if (!"XMLHttpRequest".equalsIgnoreCase(request
.getHeader("X-Requested-With"))) {// 不是ajax请求
ModelAndView view = new ModelAndView("redirect:/exception");
view.addObject("message", ex.getMessage());
return view;
} else {
return new ResponseEntity().isOk(HttpStatus.FAIL, "", ex.getMessage());
}
} else if (ex instanceof UnauthorizedException) {// shiro授权异常
if (!"XMLHttpRequest".equalsIgnoreCase(request
.getHeader("X-Requested-With"))) {// 不是ajax请求
ModelAndView view = new ModelAndView(
"redirect:/unAuthorized");
view.addObject("message", ex.getMessage());
return view;
} else {
return new ResponseEntity().isOk(HttpStatus.UNAUTHORIZED, "", "你没有操作权限");
}
} else {// 其他异常
if (!"XMLHttpRequest".equalsIgnoreCase(request
.getHeader("X-Requested-With"))) {// 不是ajax请求
ModelAndView view = new ModelAndView("redirect:/exception");
view.addObject("message", ex.getMessage());
return view;
} else {
return new ResponseEntity().isOk(HttpStatus.FAIL, "", ex.getMessage());
}
}
}
}

另外Spring的事务管理机制默认是只有捕捉到的异常是unchecked异常时事务才会回滚。
所以分为两种情况:
1.事务管理器使用默认配置
a.如果程序运行过程中出现了runtimeException.
在service层如果使用try catch捕捉异常必须再次显示的throw出来,否则事务无法回滚.
b.如果程序出现了checked异常.
则不管有没有try catch还是往上throw,事务都无法回滚,程序必须捕捉处理.
2.事务管理器配置了可回滚的异常类如rollback-for=java.lang.Excecption
如果程序运行过程中出现了runtimeException或者checked异常.
在service层如果使用try catch捕捉异常必须再次显示的throw出来,否则事务无法回滚.

总结

不管程序出现了什么类型的异常,只要使用了try catch这种方式则必须往上throw出来,从代码规范的角度看,尽量避免使用try catch的方式,要么使用throws往外抛。而绝大部分情况下出现的异常如NullPointExeption,SQLException等都是runtimeException,只有少部分情况下会出现checked异常如IOException需要显示的在代码中使用try catch或方法签名处throws出来,不然编译器编译不通过.