应用系统中的异常处理

背景

某次安防项目的晨会上。
质管小A:“安防项目今天突然不能播放视频了,点击播放按钮,提示失败,是不是底层C端出问题了!”
底层C端研发负责人小B:“没有啊,怎么可能是我们C端的问题,昨天还是好好的,是不是业务系统出问题了!”
业务系统负责人小C:“。。。。。。小明,小强你们两个排查下问题!”
小明,小强经过一阵排查,发现播放视频的配置参数被修改了,没有填写端口号。


分析

从以上的事件中可以看出,问题的关键在于系统对于异常情况的处理不到位,并且对于出现问题的提示信息过于片面化,导致问题出现时,无法迅速定位问题所在。我们开发的业务系统,或者是产品,常常面临着这样的问题:

  • 系统运行出错,但是完全不知道错误发生的位置;
  • 我们找到了错误的位置,但是完全不知道是因为什么;
  • 系统明明出了错误,但是就是看不到错误堆栈信息。

由此,J2EE Web应用系统中的异常处理显得尤为重要,优雅的以正确的方式处理异常,不仅可以提高系统健壮性的,并且还可以有效的帮助我们速度的定位排查问题。


异常的分类

从异常的分类结构看

异常分类结构图

由异常的分类结构图可以看出:Throwable类有两个直接子类java.lang.Error和java.lang.Exception,Error是无法处理的异常,一般发生这种异常,JVM会选择终止程序,编写程序时不需要关心;Exception是常见异常情况,这些异常是我们可以处理的异常,是所有异常类的父类。

从异常的受检查情况来看

从异常的受检查情况来看

从异常的受检查情况来看,可捕获的异常又可以分为两类:check异常(受查异常(checked exception))和runtime异常(非受查异常(unchecked exception))。对于非受查异常,派生自RuntimeException的异常类,对于此类异常java编译器不要求必须进行异常捕获处理或者抛出声明,由程序员自行决定;对于受查异常,直接派生自Exception的异常类,java编译器强制程序员必须进行捕获处理,否则编译都不通过。

从系统的应用角度来看

(1) 系统级异常————与应用业务逻辑无关,需要有JVM系统来处理的异常;
(2) 应用级异常————由于用户违背了商业业务逻辑而导致的错误,这种错误一般不是致命的,需要由应用系统程序本身处理。

Web应用中的异常处理机制和实现技术

异常的处理机制

异常的处理机制

Web应用中的异常处理

(1) 不要让用户看到原始的Java异常信息,也是就说禁止将异常的堆栈信息抛出到页面上;
(2) 可以将原始的Java异常信息记录到日志文件中或者输出到控制台,这样有助于程序调试中的错误定位;
(3) 在分层系统实现中的异常处理规则:
① 在分层系统实现中,下层系统向上层系统报告异常错误时,通常采用抛出自定义异常的方式实现,这样以便统一系统中不同的异常类型,例如,在项目中定义一个 AppException,然后向上层系统抛出这个异常:

1
2
3
4
5
public  class AppException extends RuntimeException{
public AppException(String message){
super(message);
}
}

② 设计自定义异常时,一般让自定义异常直接继承RuntimeException(非受查异常),从而使得上层的系统代码不必进行与异常相关的处理,这样也就使得上层程序代码不必依赖于下层的程序代码实现,这在一定程度上降低了代码的耦合度;
③ 在控制层组件中捕获用户自定义的异常,在表示层组件中处理未捕获的异常,例如,定义错误页面等;
④ 根据不同的业务场景或层级定义不同的异常类,例如,我们定义ServiceException异常类,用来表示业务逻辑受理失败,它仅表示我们处理业务的时候发现无法继续执行下去;

1
2
3
4
5
public  class ServiceException extends AppException{
public ServiceException(String message){
super(message);
}
}

(4)利用框架的相关机制处理异常,例如,spring为我们提供了ControllerAdvice机制,进行全局的 Controller 层异常处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @ControllerAdvice + @ExceptionHandler 进行全局的 Controller 层异常处理,
* 不用在 Controller 层进行 try-catch
*
*/
@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler({ ServiceException.class })
@ResponseBody
public AppResponse handleException(Exception e) {

}
}

(5) 异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多,如果考虑效率的话,可以重写Throwable的fillStackTrace方法,fillStackTrace是一个native方法,会填充异常类内部的运行轨迹;
(6) 不要用异常进行业务逻辑处理,我们提倡在业务处理的时候,如果发现无法处理直接抛出异常即可。