持续创造,加快生长!这是我参加「日新计划 10 月更文应战」的第4天,点击查看活动详情

反常是什么

反常便是一个运用程序在运转过程中,呈现了一些预期之外的故障,导致程序无法按照正常的逻辑运转,从而使一个流程中断达不到最终所要的成果。

为什么要处理反常

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

反常品种

java中反常体系结构大致如下图:

如何设计好系统异常处理

  • Throwable:作为一切反常的父类,便是说一切的反常类都是经过一次或多层次承继Throwable类完成的。
  • Error:jdk内部的反常类型,在呈现这种反常的情况那是无法处理的,就比方程序运转过程中呈现OutOfMemoryError开发人员也奈何不了,JVM就会将运用程序终止掉,这时分或许咱们就要花费大把时间排查那个对象过大导致内存溢出了,所以这一类的反常在开发过程中也无需处理。
  • Exception:首要分为运转时反常和非运转时反常两大类,非运转时反常即是在咱们代码编写完成后,运用javac命令对代码进行编译(一般Idea、Eclipse等代码编辑软件都会实时编译检测代码非运转时的反常),只有编译经过的代码才能正常进入JVM运转。
    • RuntimeException:常见的运转时反常有NullPointerException(空指针反常)、IndexOutOfBoundsException(下标越界反常)、ClassCastException(类型转化反常)、IllegalArgumentException(非法参数)等。
    • 非运转时反常:也称做查看性反常,例如ClassNotFoundException(找不到类反常)、SQLException(数据库拜访反常)等。

反常处理计划

避免反常的呈现

首要关于一些常见的反常,开发人员是能够以为控制好避免它的呈现,比方说NullPointerExceptionIndexOutOfBoundsException等等,在参数运用之前做好预防措施即可保证此类反常不会呈现。

反常捕获

关于一些开发人员无法直接预测到的反常,比方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<Object> 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%没有漏洞的,只有一次次的积累不断的把程序优化的更好,尽或许的把过错率降低,以及把过错的影响规模降至最低,最终得以保证程序能够健壮的运转下去。