一、java的异常
1.C语言的“异常”烦恼
/**
* 获取集合中指定索引的int值
*
*/
int getElementFromList(List *l, int *target, int index)
{
if (NULL ==1)
{
return -3; /*集合不能为空*/
}
if(0 == 1->len)
{
return 1; /*集合中没有元素*/
}
if (index < 0 || index > l->len-1)
{
return -1; /*参数index越界*/
}
*target = l->data[index];
return 0;
}
- 代码可读性差
- 返回值与异常值相近时,容易混淆
- 需要调用方来分析异常,增加多余的工作量
2.java语言异常处理
2.1 java异常流程

其中红线表示未处理异常
2.2 Java异常体系
通过一个机票例子说明异常体系:
Error:比如战争地震等我们不可能天天去考虑这方面事情。
Exception:
- 受检类异常:不在个人可控范围之内
- 引起注意类型
- 坦然处置型:堵车,我们可以改变路线或者早出发
- 非受检异常(Runtime Exception unchecked):
- 可预测异常:没带护照,只是提前检查即可
尽量提前检查处理: - 需捕获异常:去机场路上车抛锚,虽然难以预料,但必须得处理,可以换别的交通工具
- 可透出异常:售票机器故障,交由航空公司处理,我们不用关心
例如spring框架封装好的异常
- 可预测异常:没带护照,只是提前检查即可
注意:针对 Java编译期:checked异常在编译期会报错,非受检异常(Runtime Exception unchecked)不会报错。
问题:
- 一段try代码可能抛出多种异常,怎么捕获?
- 流程一致时 1.用父类 2.&& 、|| 方式
- 为什么调用不是本系统的异常时只能用 error和exception 的父类throwable
- 因为不知道第三方系统会抛出什么类型异常(error or exception)
3.异常处理设计与实践
3.1 异常抛出捕获原则
- 非必要不使用异常
异常主要针对非稳定性代码,避免一个try捕获整个方法代码,类似数组越界空字符串等可以提前处理的就提前处理。 - 使用描述性消息抛出异常
除了堆栈信息外 再附加当前发生异常的上下文信息(参数,环境等) - 力所能及的异常一定要处理
当前方法/层的代码是对当前异常最清楚的,如果不处理抛出将增加上层处理的成本。 - 异常忽略要有理有据
3.2 【try…catch…finally】流程解析

注:try和catch的优先级是一样的,都会在finally语句之前,如果有catch语句块,return的逻辑与流程图一致。
对应流程图看一段代码,下面方法1和方法2代码返回值分别是1和2,说明finally语句块修改不了try/catch中用来存放return返回值的局部变量。
private int finallyReturn() {
int i = 0;
try {
i = 1;
return i;
} finally {
i = 10;
}
}
private int finallyReturn2() {
int i = 1;
try {
return i/0;
}catch (ArithmeticException e){
// e.printStackTrace();
i = 2
return i;
}finally {
i = 10;
}
}
最后修改以下代码用来解释规约“不要在finally块中使用return”。运行代码会发现2个方法return结果都是10,finally中return会忽略try/catch中的返回值。
private int finallyReturn() {
int i = 0;
try {
i = 1;
return i;
} finally {
i = 10;
return i;
}
}
private int finallyReturn2() {
int i = 1;
try {
return i/0;
}catch (ArithmeticException e){
// e.printStackTrace();
i = 2
return i;
}finally {
i = 10;
return i;
}
}
3.3 关于【try with resource】
try with resource流程

try with resource需要注意的问题,如下代码 。
try (FileInputStream fin = new FileInputStream(new File("download urls.txt"));
// 这里的FileOutputStream并不会正常close,需要肚里起一行
// GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(new File("out.txt")))
FileOutputStream fout = new FileOutputStream(new File("out.txt"));
GZIPOutputStream out = new GZIPOutputStream(fout)
) {
byte[] buffer = new byte[4 * 1024];
int read;
while ((read = fin.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
} catch (Exception e) {
e.printStackTrace();
}
3.4 NPE处理
- 级联调用时可以使用Optional来处理
JDK8的Optional类可以优雅的防止连续属性调用出现的NPE问题 ,(这块有时间要深入研究内部原理) - 特殊异常场景及其处理对策
foreach遍历集合的异常- 不要再foreach循环里进行元素的remove/add操作
- foreach循环会自动跳过遍历空集合 ,如果对于有null的集合,碰到null时需要注意NPE
二、日志
日志命名:
- 当天日志命名:以“应用名.log”来保存,
- 过往日志命名:以 {logname}.log.{保存日期}命名 ,日期格式标准为yyyy-MM-dd
- 日志保存实践:
- 至少保存15天, 便于排查某些以周为频次发生的异常
- 敏感操作信息联机存储6个月(网络安全法规)
日志规约
- 系统应依赖使用日志框架(SLF4J,JCL)的API而不是具体日志库中的API
- 在日志输出时,字符串变量之间的拼接使用占位符的方式
- 日志打印时禁止直接用JSON工具将对象转换成String
- 尽量用英文 来描述日志信息
- logback框架使用之核心配置对象及属性分析
日志输出规则
- 日志级别开关判断
对于trace/debug/info级别的日志输出,必须进行日志级别的开关判断 - 异常日志信息要完整
异常信息应该包含 案发现场信息和堆栈异常信息 - 避免重复打印日志
应避免重复日志,重复日志会浪费磁盘空间,注意在配置中设置additivity=false - 扩展日志和错误日志存储规范
- 扩展日志应该单独存储,如应用中的(打点、临时监控、访问日志等)
- 业务日志应该与错误日志分开存储
错误码规约
错误码的功能:简单易容的让人知晓错误来源,快速判断是谁的问题,使团队快速对错误原因达成一致。增进任何系统
错误码规则
- 定义时要有字母也要有数字
- 要分级分类管理
- 不能这直接输出给用户座位提示信息使用
- 不要与业务架构或者组织架构挂钩
- 使用者避免随意定义新的错误码
- 便于不同语言的开发者之间协作
三、综合实践
1.在项目Controller层统一捕获异常
注意在分布式部署成多台机器时,异常日志需要在每层单独记录。
2.全局异常处理组件的定义和使用
可以使用@RestControllerAdvice 进行统一拦截,其中区分业务异常(车票不足、用户已注册等) 全局异常(RPC请求超时、RPC服务异常等)
3.API层异常涉及实践
- 严格约束条件判断:API层要严格校验保证进入系统的数据是合法合规的
- 基本判断约束:null值等基本判断
- 实体属性约束 :满足JSR303基础判断
- 客户端返回要友好
- API层异常要给客户端返回状态码及其对应的错误消息
- 下层异常转移:service、manager层异常转移成API层异常
- 错误码文档要规范
- 系统状态码对应的异常或错误信息以及可能发生异常的原因,要整理成便于用户查阅的文档,同步给接口调用方
4.Service层异常涉及实践
-
严格约束条件判断避免脏数据
- 基本判断约束:null值等基本判断
- 实体属性约束 :满足JSR303基础判断
- 业务条件越苏:需求提出的不同的业务约束
-
抛出指定类型的异常
- service层抛出带状态码或指定类型的异常
-
转译DAO异常
- 将DAO层的异常转译为Service层或者更高层能够理解的异常
5.DAO数据处理层异常日志实践
-
通用DaoException
使用继承自Runtime Exception的通用DaoException封装Dao层异常并向上抛出
-
框架层面有选择性的记录数据操作
在DAO层(框架层面)有选择的记录数据操作的有效信息,比如:每次操作的原始SQL语句及其执行时间
6.链路追踪
分布式链路追踪:将一次分布式请求还原成调用链路,将一次分布式请求的调用情况集中展示(服务器上节点耗时,每个服务节点请求状态等)
链路跟踪主要功能:快速定位故障、性能可视化、链路分析
7.用有限的理业类处理业务中复杂多变的无限可能多变的无限可能
-
通用ServiceException
定义继承自RuntimeEXception的通用ServiceException业务异常
-
结合ErrorCode
结合与业务关联的ErrorCode实现复杂多变的业务异常需求
8.降低系统的维护难度与国度涉及、冗余的手段
系统维护由代码的维护、系统运营的维护组成。
-
通用模块单独拆分
-
合理的领域划分
-
合适的工程结构
四、总结
异常、日志的完善是区分demo和企业级项目明显的标志,通过健全的异常机制保护系统的健壮性,通过完备日志记录和存储保证系统问题排查的精准高效。

被折叠的 条评论
为什么被折叠?



