异常处理规范

目录

  • [1 简介](#1 简介)
    • [1.1 目的](#1.1 目的)
    • [1.2 名词解释](#1.2 名词解释)
  • [2 原则](#2 原则)
    • [2.1 定义异常](#2.1 定义异常)
    • [2.2 异常捕获原则](#2.2 异常捕获原则)
      • [2.2.1 基本原则](#2.2.1 基本原则)
      • [2.2.1 业务处理](#2.2.1 业务处理)
      • [2.3 异常分级](#2.3 异常分级)
      • [2.4 异常分类](#2.4 异常分类)
      • [2.5 异常传递原则](#2.5 异常传递原则)
      • [2.6 异常处理场景](#2.6 异常处理场景)
      • [2.7 告警处理](#2.7 告警处理)
  • [3 统一异常编码](#3 统一异常编码)
    • [3.1 编码规则](#3.1 编码规则)
  • [4 异常归集](#4 异常归集)
    • [4.1 异常处理](#4.1 异常处理)
    • [4.2 异常收集](#4.2 异常收集)
  • [5 异常处理场景示范](#5 异常处理场景示范)
    • [5.1 基本原则](#5.1 基本原则)
      • [5.1.1 RuntimeException异常](#5.1.1 RuntimeException异常)
      • [5.1.2 try-catch 作用域](#5.1.2 try-catch 作用域)
      • [5.1.3 异常捕获正确处理](#5.1.3 异常捕获正确处理)
      • [5.1.4 异常资源释放](#5.1.4 异常资源释放)
      • [5.1.5 finally 块](#5.1.5 finally 块)
    • [5.2 业务处理](#5.2 业务处理)
      • [5.2.1 异常处理架构](#5.2.1 异常处理架构)
      • [5.2.2 用户访问场景](#5.2.2 用户访问场景)
      • [5.2.3 rpc服务端](#5.2.3 rpc服务端)

1 简介

1.1 目的

异常处理规范的目的,规范解决几个问题:

  • 不同场景如何抛出异常;
  • 抛出什么类型异常;
  • 异常如何处理;
  • 如何友好的展示或输出;
  • 异常出现后,该如何加快问题解决;

1.2 名词解释

  • controller:与fe交互、或提供rest服务
  • rpc服务:对外提供rpc服务
  • rpc客户:提供对远程rpc封装,用于本系统
  • service:定义service,用于向controller层或rpc服务层提供服务
  • dao:访问底层数据库程序集合
  • remote:访问远程系统程序集合
  • 外部服务:其他系统提供的服务,需通过http或rpc访问
  • 对外接口:本系统向其他系统提供的服务

2 原则

2.1 定义异常

  1. 对某类场景,有特殊处理需求时,可以定义异常;
  2. 有特殊显示需求时,可以定义异常;
  3. 为了区分某类错误时,可以定义异常;
  4. 底层异常不够友好、不方便分析判断时,可以定义异常;
  5. 正常业务逻辑,不允许使用异常来定义;
  6. 无特殊原因,直接向上抛出原始异常即可;

2.2 异常捕获原则

2.2.1 基本原则

  1. Java类库中定义的继承自 RuntimeException 的运行时异常类如:IndexOutOfBoundsException / NullPointerException ,这类异常由程序员预检查来规避,保证程序健壮性;
  2. try-catch 作用域:(1)禁止大段代码进行try-catch;(2)catch 时分清稳定代码(无论如何不会出错的代码)和非稳定代码;(3)非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理;
  3. 优先使用明确异常;
  4. 异常捕获需正确处理:(1)禁止捕获后,抛弃;(2)不处理,可以直接向后抛出;(3)最外层业务使用者,转化为易于理解信息;
  5. 异常事务:(1)事务代码中使用try-catch ,如手动回滚,在catch中执行;(2)事务的代码中,catch 异常后,如自动回滚,需继续抛出异常;
  6. 异常资源释放:IO流、文件访问等资源逐一释放;
  7. finally 块:(1)禁止执行 return;(2)finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句;(3)导致混乱;
  8. NPE异常(空指针):(1)数据库的查询结果可能为 null;(2)远程调用对象时,一律要求进行空指针判断,防止 NPE;(3)对于下层服务 中获取的数据,建议 NPE 检查,避免空指针;(4)级联调用 obj.getA().getB().getC(),一连串调用,易产生 NPE;

2.2.1 业务处理

  1. 对外接口,必须捕获异常,打印堆栈,转化为易于理解输出;
  2. 内部调用外部服务,要求捕获异常,如果为内部定义异常直接抛出,否则转为内部异常,抛出;
  3. 内部服务处理:(1)对特定异常有处理需求时捕获,否则,直接向上抛出;(2)循环调用时,如果系统允许单条异常(默认不推荐),内部捕获,打印堆栈;如果不允许直接抛出异常;
  4. 服务层,要求捕获异常,如果为内部定义异常直接抛出,否则转为内部异常,抛出;
  5. 定时任务等服务,调用,要求捕获异常,打印堆栈;
  6. 其他场景,一般不捕获,直接抛出异常;

2.3 异常分级

  • 分级目的:方便后续根据异常级别定义各种规则,如:是否告警、是否需要转化、是否打印堆栈信息。
  • 分级依据
    • 根据抛出异常具体原因,如果需要研发、运维参与解决的异常,级别必须为:CONFIG、NET、SYSTEM、NORMAL和UNKNOWN;
    • INFO异常,是在符合系统预期时,提供信息展示、数据返回时,提供的一种处理方式,是设计得来的结果,简单来说,故意为之,才可能是INFO级别;
    • WARN异常,一般用于检查用户输入、用户错误操作、无权限等,给出提示即可;

2.4 异常分类

2.5 异常传递原则

在开发过程中,会涉及多个系统间调用,底层是mysql异常、中间经过rpc传输、之后是远端服务、再之后经过逻辑处理,遵循以下原则:

(1)系统间异常传递

  • 调用方:在系统间调用,client端即为调用方;
  • 调用方职责:记录异常(异常堆栈、参数信息),按照发起方使用场景进行转化;
  • 提供方:系统间调用,server端即为提供方;
  • 提供方职责:记录异常,返回异常信息给调用方,异常信息中包含底层异常原因;

(2)系统内异常传递

  • 调用方:上层调用下层,上层即为调用方;
  • 调用方职责:记录异常(异常堆栈、参数信息),按照发起方使用场景进行转化;
  • 提供方:下层服务,即为提供方;
  • 提供方职责,默认不对异常进行转化、处理,特殊需求时可以处理,但必须向上抛出可以追踪的异常;

(3)原始异常

  • 原始异常:系统自带异常,如mysql、jdk自带等;
  • 原始异常处理
    • 网络连接或读取异常(未获得连接、访问被拒绝等),需自定义处理,为后续重试提供统一依据;
    • 系统异常,如表不存在、文件不存在等,这些底层jdk、mysql驱动抛出的异常,不做处理直接向上抛出;

2.6 异常处理场景

2.7 告警处理

3 统一异常编码

3.1 编码规则

  1. 建议短而有含义;
  2. 英文大写字母、_(下划线)、数字;
  3. 长度不超过20个字符;

4 异常归集

4.1 异常处理

  1. 系统内部要求,有统一处理异常逻辑;
  2. 在统一处理异常处,集中上报;
  3. 系统内部打印日志,输出异常,提供问题分析依据;
  4. 对异常增加标记,明确是否被处理、处理人、处理结果等;

4.2 异常收集

  1. 要求有异常收集服务;
  2. 支持持久存储,按天存储,根据分级、分类、编码、traceId可进行异常查询;
  3. 可定义异常告警规则,对收集的异常进行告警处理(短信、电话、邮件、hi);
  4. 按时间维度(天、周、月)、系统维度,发告警报告;

5 异常处理场景示范

5.1 基本原则

5.1.1 RuntimeException异常

正确做法:

java 复制代码
if(null != obj) {
    //TODO
}

错误做法:

java 复制代码
try {
    obj.method()
} catch(NullPointerException e){
   //TODO
}

5.1.2 try-catch 作用域

正确做法:

java 复制代码
try{
    //代码省略  
} catch (UnknownHostException e) {
    //域名错误
} catch (SocketTimeoutException e) {
    //超时
} catch (Exception e) {
    //其他异常
}

错误做法:

java 复制代码
try {
   //此处省略1024行代码
} catch(Exception e){
   //TODO
}

5.1.3 异常捕获正确处理

正确做法:

java 复制代码
//自行处理
try{
    //代码省略
} catch (UnknownHostException e) {
    //域名错误,处理
} catch (SocketTimeoutException e) {
    //超时,处理
} catch (Exception e) {
    //其他异常,处理
}
//继续抛出
try{
    //代码省略
} catch (UnknownHostException e) {
    throw new HNException("xxx处理失败。",e);
}

错误做法:

java 复制代码
//吞掉异常,无法定位
try{
    //代码省略
} catch (Exception e) {
    System.out.println("插入异常");
}
 
//原始异常抛弃,无法跟踪
try{
    //代码省略
} catch (UnknownHostException e) {
    throw new RuntimeException("500");
}

5.1.4 异常资源释放

正确做法:

java 复制代码
try{
    //代码省略
} catch (Exception e) {
    throw new XXException("描述",e);
}finally{
              
    try {
        if(null != conn){
            conn.disconnect();
        }
    } catch (Exception e1) {
        //处理
    }
    try {
        if(null != outStream){
            outStream.close();
        }
    } catch (Exception e2) {
        //处理
    }
    try {
        if(null != out){
            out.close();
        }
    } catch (Exception e3) {
        //处理
    }
}

错误做法:

java 复制代码
try{
    //代码省略
} catch (Exception e) {
    throw new XXException("描述",e);
}finally{
    try {
      conn.disconnect();
      outStream.close();
      out.close();
    } catch (Exception e1) {
        //处理
    }
}

5.1.5 finally 块

正确做法:

java 复制代码
try{
    //代码省略
    return "OK";
 } catch (Exception e) {
    throw new XXException("描述",e);
}finally{
 
}

错误做法:

java 复制代码
//读这样的代码,很伤脑子
try{
    //代码省略
    return "OK";
 } catch (Exception e) {
    throw new XXException("描述",e);
}finally{
    reutrn "BAD";
}

5.2 业务处理

5.2.1 异常处理架构

5.2.2 用户访问场景

  1. 场景说明:前端、cotroller、service、dao
  2. 整体约定
    • 前端提供异常时展示;
    • controller捕获下层异常,按约定异常时数据格式,输出数据;
    • service、dao不做异常捕获,直接抛出底层异常;
  3. controller编码
    controller:添加注解@ExtExceptionCollect,用于统一收集异常
java 复制代码
/**
 * 数字转化演示
 *  * @param str
 * @return
 */
@GetMapping("/num")
@ResponseBody
@ExtExceptionCollect(handlerBean = "controllerExceptionHandler")
public String num(@RequestParam("num") String str) {
    LOGGER.info("input:{}", str);
    int n = samplesNumService.findNum(str);
    LOGGER.info("result:{}", n);
    return "OK:" + n;
}

增加ControllerExceptionHandler

  • 处理未知异常
  • 转化已有异常为用户易于理解,友好提示信息
java 复制代码
/**
 * controller层异常处理
 *
 *
 */
@Service
public class ControllerExceptionHandler {
    private static Logger LOGGER = LoggerFactory.getLogger(ControllerExceptionHandler.class);
 
    /**
     * 未知异常处理
     *
     * @param exceptionArgs
     * @return
     */
    @ExtExceptionHandler(Exception.class)
    public String unknownHandler(ExceptionArgs exceptionArgs) {
        LOGGER.error("ERROR", exceptionArgs.getException());
        return "Unknown Error:" + exceptionArgs.getException().getMessage();
    }
 
    /**
     * 已知异常,转为前端信息
     *
     * @param exceptionArgs
     * @return
     */
    @ExtExceptionHandler(value = ExtException.class)
    public String controllerHandler(ExceptionArgs exceptionArgs) {
        LOGGER.error("Error", exceptionArgs.getException());
        ExtException exception = (ExtException) exceptionArgs.getException();
        return "出现错误:" + exception.getDesc();
    }
}
  1. service编码
    添加注解@ExtExceptionCollect,用于统一收集异常
java 复制代码
@Service
public class SamplesNumService {
    private static Logger LOGGER = LoggerFactory.getLogger(SamplesNumService.class);
 
    @ExtExceptionCollect(handlerBean = "serviceExceptionHandler")
    public int findNum(String str) {
        SamplesNumDao convert = new SamplesNumDao();
        int n = convert.load(str);
        LOGGER.info("load result:{}", n);
        return n;
    }
}

增加ServiceExceptionHandler

  • 处理未知异常
  • 已有异常直接抛出
java 复制代码
/**
 * service层异常处理
 *
 *
 */
@Service
public class ServiceExceptionHandler {
    /**
     * 未知异常处理
     *
     * @param exceptionArgs
     * @return
     */
    @ExtExceptionHandler(Exception.class)
    public String otherHandler(ExceptionArgs exceptionArgs) {
        throw new ExtBizException(SamplesExceptionTypes.SERVICE_UNKOWN, "", exceptionArgs.getException());
    }
 
    /**
     * 已知异常,直接抛出
     *
     * @param exceptionArgs
     * @return
     */
    @ExtExceptionHandler(value = ExtException.class)
    public String customHandler(ExceptionArgs exceptionArgs) {
        ExtException exception = (ExtException) exceptionArgs.getException();
        throw exception;
    }
}

5.2.3 rpc服务端

  1. 异常码定义
java 复制代码
RPC_NET("访问Rpc服务网络相关异常", ExceptionLevel.NET, ExceptionType.BIZ_OTHER), //
RPC_HELLO("访问Rpc服务hello错误", ExceptionLevel.SYSTEM, ExceptionType.BIZ_OTHER), //
RPC_HELLO_UNKOWN("访问Rpc服务hello未知错误", ExceptionLevel.NET, ExceptionType.BIZ_OTHER), //
  1. 客户端访问函数增加注解:ExtExceptionCollect
java 复制代码
@ExtExceptionCollect(handlerBean = "rpcHelloClientExceptionHandler")
public String hello(String str) {
    try {
        return helloWorldService.hello(str);
    } catch (ExtException e) {
        throw e;
    } catch (Exception e) {
        throw new ExtRemoteException(SamplesExceptionTypes.RPC_HELLO, "str:" + str, e);
    }
}

(3)客户端定义异常处理类

java 复制代码
/**
 * rpcClient层异常处理
 *
 *
 */
@Service
public class RpcHelloClientExceptionHandler {
    /**
     * 未知异常处理
     *
     * @param exceptionArgs
     * @return
     */
    @ExtExceptionHandler(Exception.class)
    public String otherHandler(ExceptionArgs exceptionArgs) {
        throw new ExtBizException(SamplesExceptionTypes.RPC_HELLO_UNKOWN, exceptionArgs.getException().getMessage(),
                exceptionArgs.getException());
    }
 
    /**
     * 已知异常,直接抛出
     *
     * @param exceptionArgs
     * @return
     */
    @ExtExceptionHandler(value = ExtSysException.class)
    public String customSysHandler(ExceptionArgs exceptionArgs) {
        ExtException exception = (ExtException) exceptionArgs.getException();
        throw new ExtRemoteException(SamplesExceptionTypes.RPC_NET, exception.getMessage(), exception);
    }
 
    /**
     * 已知异常,直接抛出
     *
     * @param exceptionArgs
     * @return
     */
    @ExtExceptionHandler(value = ExtException.class)
    public String customHandler(ExceptionArgs exceptionArgs) {
        ExtException exception = (ExtException) exceptionArgs.getException();
        throw new ExtRemoteException(SamplesExceptionTypes.RPC_HELLO, exception.getMessage(), exception);
    }
}
相关推荐
码上一元1 小时前
SpringBoot自动装配原理解析
java·spring boot·后端
计算机-秋大田2 小时前
基于微信小程序的养老院管理系统的设计与实现,LW+源码+讲解
java·spring boot·微信小程序·小程序·vue
魔道不误砍柴功3 小时前
简单叙述 Spring Boot 启动过程
java·数据库·spring boot
失落的香蕉4 小时前
C语言串讲-2之指针和结构体
java·c语言·开发语言
枫叶_v4 小时前
【SpringBoot】22 Txt、Csv文件的读取和写入
java·spring boot·后端
wclass-zhengge4 小时前
SpringCloud篇(配置中心 - Nacos)
java·spring·spring cloud
路在脚下@4 小时前
Springboot 的Servlet Web 应用、响应式 Web 应用(Reactive)以及非 Web 应用(None)的特点和适用场景
java·spring boot·servlet
黑马师兄4 小时前
SpringBoot
java·spring
数据小小爬虫4 小时前
如何用Java爬虫“偷窥”淘宝商品类目API的返回值
java·爬虫·php
暮春二十四4 小时前
关于用postman调用接口成功但是使用Java代码调用却失败的问题
java·测试工具·postman