如何设计好系统异常处理

异常是什么
异常就是一个应用程序在运行过程中,出现了一些预期之外的故障,导致程序无法按照正常的逻辑运行,从而使一个流程中断达不到最终所要的结果。

为什么要处理异常
这个最直接的答案就是为了保障程序能够正常运行。不管是一个简单或复杂的应用程序,它的出现都是有一定的道理,能够解决相关的问题(产品、客户需求)。相信任何一个开发人员都无法保证一开始程序就100%没有问题,更何况体量稍大的应用都会依赖一些第三方服务,或者我们常见的微服务也是,一旦我们的程序对外部有依赖就避免不了异常的发生,原因是外部依赖的服务不是我们的可控范围,这时候就必须处理异常,也就是通过异常处理将应用程序的故障降到最低。就像程序过多的依赖中间件,如果出现异常没有做好保护措施,那么可能导致服务器宕机数据库挂掉都有可能。

异常种类
在java中异常体系结构大致如下图:

Throwable:作为所有异常的父类,就是说所有的异常类都是通过一次或多层次继承Throwable类实现的。
Error:jdk内部的异常类型,在出现这种异常的情况那是无法处理的,就比如程序运行过程中出现OutOfMemoryError开发人员也奈何不了,JVM就会将应用程序终止掉,这时候可能我们就要花费大把时间排查那个对象过大导致内存溢出了,所以这一类的异常在开发过程中也无需处理。
Exception:主要分为运行时异常和非运行时异常两大类,非运行时异常即是在我们代码编写完成后,使用javac命令对代码进行编译(通常Idea、Eclipse等代码编辑软件都会实时编译检测代码非运行时的异常),只有编译通过的代码才能正常进入JVM运行。
RuntimeException:常见的运行时异常有NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)、ClassCastException(类型转换异常)、IllegalArgumentException(非法参数)等。
非运行时异常:也称做检查性异常,例如ClassNotFoundException(找不到类异常)、SQLException(数据库访问异常)等。
异常处理方案
防止异常的出现
首先对于一些常见的异常,开发人员是可以认为控制好防止它的出现,比如说NullPointerException、IndexOutOfBoundsException等等,在参数使用之前做好预防措施即可保证此类异常不会出现。

异常捕获
对于一些开发人员无法直接预测到的异常,比如jdk内部或第三方框架抛出的检查性异常我们必须进行异常捕获,并对异常进行处理,得到我们想要的结果,如下一段读取文件内容的代码:

private static String readFile(String path) {
File file = new File(path);
FileInputStream fis;
// 创建FileInputStream会打开一个文件,程序会显示的检测异常,提示需要对文件不存在异常进行处理,这里就需要主动进行异常捕获,否则就必须使用throws对该异常向上一层抛出,供调用者去处理
try {
fis = new FileInputStream(file);
}
catch (FileNotFoundException e) {
log.error(“file is not found.”);
return null;
}
// 正常继续执行
StringBuilder sb = new StringBuilder();
// 读取文件流的时候,程序会显示的检测可能出现IO异常,开发人员就必须对此异常进行捕获处理
try {
byte[] bt = new byte[1024];
int read;
while ((read = fis.read(bt)) != -1) {
sb.append(new String(bt));
}
}
catch (IOException e) {
log.error(“file read exception.”);
return null;
}
// 同理关闭文件的时候也可能出现IO异常需要处理
try {
fis.close();
}
catch (IOException e) {
log.error(“file close exception.”);
}
return sb.toString();
}
以上的代码块都是简单的捕获异常,并进行一系列的日志输出,让程序正常运行下去,调用者可以根据返回结果是否为null判断是否有文件内容要输出。

异常管理
通常应用程序出现问题,用户在页面上是不想看到一堆杂乱不齐的代码,看也看不懂什么意思,这时候我们通常需要对异常进行一个统一管理,开发人员可以前后端约定好接口成功以及失败的代码编号。

比如说接口访问成功就返回code=success,前端也可以根据访问成功进行下一步的流程。
除了code=success其余的均为失败,失败也可以根据特定的代码进行划分,前端也可对特定的异常类型做处理,比如说用户未登录后端返回code=unlogin,前端检测到此代码即可直接跳转至登录页。
全局异常处理
在Spring应用程序中我们可以配置一个全局的异常拦截方案,使用@ControllerAdvice注解设计一个异常处理类GlobalExceptionHandler,结合上一点异常管理对异常再做进一步优化。如下定义一个异常公共类:

public class BaseException extends RuntimeException {
// 错误代码
private String code;
// 错误内容
private String message;

public BaseException(String code, String message) {
    this.code = code;
    this.message = message;
}

}
如果业务模块划分比较多,我们还可以继承BaseException类实现业务侧特殊的异常输出。紧接着如下对业务异常统一管理:

@ControllerAdvice
public class GlobalExceptionHandler {
// 配置拦截特定异常类型
@ExceptionHandler(BaseException.class)
@ResponseBody
// 返回对应的http状态为200,前端根据返回的code做处理
@ResponseStatus(HttpStatus.OK)
public CommonResult baseExceptionHandler(BaseException exception) {
// log输出日志文件记录
log.error("baseExceptionHandler: ", exception);
return CommonResult.builder()
.code(exception.getCode())
.msg(exception.getMessage()).build();
}

// 无法预料的异常,就按Exception类进行拦截,并返回错误代码999999
@ResponseBody
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.OK)
public CommonResult<Object> exceptionHandler(Exception exception) {
    log.error("exceptionHandler: ", exception);
    // 没有错误信息,则使用默认错误信息填充
    String message = exception.getMessage();
    return CommonResult.builder()
            .code("999999")
            .msg(StringUtils.isEmpty(message) ? "system error!" : message).build();
}
// 更多的异常类型需要进行独特的处理可以参照上面的进行配置,主要还是要跟前端人员配合好

}
总结
没有哪个程序100%没有漏洞的,只有一次次的积累不断的把程序优化的更好,尽可能的把错误率降低,以及把错误的影响范围降至最低,最终得以保证程序能够健壮的运行下去。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值