目录
- [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 定义异常
- 对某类场景,有特殊处理需求时,可以定义异常;
- 有特殊显示需求时,可以定义异常;
- 为了区分某类错误时,可以定义异常;
- 底层异常不够友好、不方便分析判断时,可以定义异常;
- 正常业务逻辑,不允许使用异常来定义;
- 无特殊原因,直接向上抛出原始异常即可;
2.2 异常捕获原则
2.2.1 基本原则
- Java类库中定义的继承自 RuntimeException 的运行时异常类如:IndexOutOfBoundsException / NullPointerException ,这类异常由程序员预检查来规避,保证程序健壮性;
- try-catch 作用域:(1)禁止大段代码进行try-catch;(2)catch 时分清稳定代码(无论如何不会出错的代码)和非稳定代码;(3)非稳定代码的 catch 尽可能进行区分异常类型,再做对应的异常处理;
- 优先使用明确异常;
- 异常捕获需正确处理:(1)禁止捕获后,抛弃;(2)不处理,可以直接向后抛出;(3)最外层业务使用者,转化为易于理解信息;
- 异常事务:(1)事务代码中使用try-catch ,如手动回滚,在catch中执行;(2)事务的代码中,catch 异常后,如自动回滚,需继续抛出异常;
- 异常资源释放:IO流、文件访问等资源逐一释放;
- finally 块:(1)禁止执行 return;(2)finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句;(3)导致混乱;
- NPE异常(空指针):(1)数据库的查询结果可能为 null;(2)远程调用对象时,一律要求进行空指针判断,防止 NPE;(3)对于下层服务 中获取的数据,建议 NPE 检查,避免空指针;(4)级联调用 obj.getA().getB().getC(),一连串调用,易产生 NPE;
2.2.1 业务处理
- 对外接口,必须捕获异常,打印堆栈,转化为易于理解输出;
- 内部调用外部服务,要求捕获异常,如果为内部定义异常直接抛出,否则转为内部异常,抛出;
- 内部服务处理:(1)对特定异常有处理需求时捕获,否则,直接向上抛出;(2)循环调用时,如果系统允许单条异常(默认不推荐),内部捕获,打印堆栈;如果不允许直接抛出异常;
- 服务层,要求捕获异常,如果为内部定义异常直接抛出,否则转为内部异常,抛出;
- 定时任务等服务,调用,要求捕获异常,打印堆栈;
- 其他场景,一般不捕获,直接抛出异常;
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 编码规则
- 建议短而有含义;
- 英文大写字母、_(下划线)、数字;
- 长度不超过20个字符;
4 异常归集
4.1 异常处理
- 系统内部要求,有统一处理异常逻辑;
- 在统一处理异常处,集中上报;
- 系统内部打印日志,输出异常,提供问题分析依据;
- 对异常增加标记,明确是否被处理、处理人、处理结果等;
4.2 异常收集
- 要求有异常收集服务;
- 支持持久存储,按天存储,根据分级、分类、编码、traceId可进行异常查询;
- 可定义异常告警规则,对收集的异常进行告警处理(短信、电话、邮件、hi);
- 按时间维度(天、周、月)、系统维度,发告警报告;
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 用户访问场景
- 场景说明:前端、cotroller、service、dao
- 整体约定
- 前端提供异常时展示;
- controller捕获下层异常,按约定异常时数据格式,输出数据;
- service、dao不做异常捕获,直接抛出底层异常;
- 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();
}
}
- 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服务端
- 异常码定义
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), //
- 客户端访问函数增加注解: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);
}
}