Java异常体系

Java异常机制

使用异常、日志为系统保驾护航

道路千万条,安全第一条

日志不规范,排查两行泪

异常应当描述导致当前异常发生的原因

根据异常栈快速定位倒异常发生的位置

结合异常描述和异常栈解决异常

日志记录规约

系统应依赖使用日志框架(SLF4J、JCL)的API而不是具体日志库中的API

SLF4j绑定具体日志框架的过程
  1. 系统应依赖使用日志框架(SLF4J、JCL)的API而不是具体日志库中的API
  2. 在日志输出时,字符串变量之间的拼接使用占位符的方式
  3. 日志打印时禁止直接用JSON工具将对象转换为String
  4. 尽量用英文来描述日志错误信息
logback框架使用之核心配置对象及属性分析
logback日志框架使用之配置文件解析

根节点-configuration及其通用属性的配置示例

Appender节点配置示例

logback日志框架使用之关键类图分析

异步Appender配置示例

logback日志记录线程模型分析

异常错误日志实时通知

将错误异常日志发送至邮箱与企业微信

整合Sentry将错误日志发送至钉钉消息

日志输出规约

日志级别开关判断

异常信息要完整

避免重复打印日志

扩展日志设计与规约

T31日志设计标准

基于以上我们的日志设计将按以下标准来设计

1、系统应依赖使用日志框架(SLF4J、JCL)的API而不是具体日志库中的API,这样有利于哦我们维护和统一各个类的日志处理方式

import org.slf4j.Logger; 
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(App.class);

2、为了检查异常错误是否是在应用运行期间周期性发送,我们的日志文件起码要保存两周以上。

3、应用中的扩展日志的命名方式应该统一 :

appName_logType_logName.log。logType:日志类型,推荐分类有
stats/desc/monitor/visit 等;logName:日志描述。这种命名的好处:通过文件名就可知道日志文件属于什么应用,什么类型,什么目的,也有利于归类查找

例如:

T31server应用中余票监控时区转换异常,如:

T31server_monitor_timeZoneConvert.log

我们为了方便查看和便于通过日志对系统进行及时监控,应该对日志进行分类,比如将错误日志和业务日志分开存放

4、级别日志输出(trace/debug/info)必须使用条件输出形式或者使用占位符的方式。

说明:logger.debug("Processing trade with id: " + id + " symbol: " + symbol);如果日志级别是 warn,上述日志不会打印,但是会执行字符串拼接操作,如果 symbol 是对象, 会执行 toString()方法,浪费了系统的计算资源,就算执行了上述操作,可日志却也没有打印。

例如:

使用条件输出形式

if (logger.isDebugEnabled()) { 
logger.debug("Processing trade with id: " + id + " symbol: " + symbol); 
}

使用占位符的方式

logger.debug("Processing trade with id: {} symbol : {} ", id, symbol); 

5、为了避免重复打印日志,造成磁盘IO浪费,所以一定要做log4j.xml中设置additivity=false。

<logger name="com.T31.dubbo.config" additivity="false"> 

6、异常信息必须包括两类信息:系统异常现场信息和异常堆栈信息。如果不处理,那就要关键字 throws 往上抛出。

logger.error(各类参数或者对象 toString + "_" + e.getMessage(), e);

7、对于日志的使用要谨慎。生产环境禁止输出debug日志;对于info日志要有选择的输出;warn级别的日志要注意日志输出量的问题,避免把服务器磁盘占满,及时删除这些观察日志。

错误码设计文档

错误码的功能和应用

  1. 错误码暂定都是5位数字,并配有相应的英文解释
  2. 错误码为 0 表示成功,其他都表示错误
  3. 错误码按模块按功能场景分级分段,前三位错误码表示模块,第四位表示模块下的功能。举例,商城系统里有交易模块和商品模块,则可以这样划分:401开头的表示交易模块,402开头的表示商品模块,4011开头的表示交易模块里的下单场景需要用到的错误码,4021表示商品模块下的添加商品场景里需要用到的错误码。如果某个场景功能下需要的比较多的错误码,则可以使用其他未被使用的码段,即该场景功能可以拥有多个码段,然后通过添加注释等方式让人理解即可。
  4. 数字 1 开头的错误码表示系统级别的错误,比如缺少某种字符集,连不上数据库之类的,系统级的错误码不需要分模块,可以按照自增方式进行添加
  5. 数字 4 开头的错误码表示API参数校验失败,比如 交易模块下单场景中,订单金额参数不能为空 可以用 40111 错误码来表示
  6. 数字 5 开头的错误码表示后台业务校验失败,比如 交易模块下单场景中,该用户没有下单权限 可以用 50111 错误码来表示
  7. 数字 4 开头的错误码与数字 5 开头的错误码对应的模块分类需要保持一致,即 4011 表示交易模块下单场景的API错误,5011 表示交易模块下单场景的业务错误
  8. 错误码按需分配,逐步增加,灵活扩展

错误码规约

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A71o5mmJ-1635754163694)(D:\大数据\笔记\孤尽\T31.assets\image-20211101152244310.png)]

T31错误码设计文档

错误码举例

一、授权/令牌请求接口返回码

描述应用发起授权请求或令牌请求时,开放平台的返回码。

错误码错误描述Error Description
10000非法的请求参数Invalid request
10001用户认证失败Invalid client
10002非法的授权信息Invalid grant
10003应用没有被授权,无法使用所指定的grant_typeUnauthorized client
10004grant_type字段超过定义范围Unsupported grant_type
10005scope信息无效或超出范围Invalid scope
10006提供的更新令牌已过期Expired token
10007redirect_uri字段与注册应用时所填写的不匹配Redirect_uri mismatch
10008response_type参数值超过定义范围Unsupported response type
10009用户或授权服务器拒绝授予数据访问权限Access denied
二、API通用返回码

描述API接口的共性返回码

错误码错误描述Error Description
0成功Success
1未知错误Unknown error
2服务暂不可用Service temporarily unavailable
3未知的方法Unsupported openapi method
4接口调用次数已达到设定的上限Open api request limit reached
5请求来自未经授权的IP地址Unauthorized client IP address
6无权限访问该用户数据No permission to access user data
7来自该refer的请求无访问权限No permission to access data for this referer
100请求参数无效Invalid parameter
101api key无效Invalid API key
104无效签名Incorrect signature
105请求参数过多Too many parameters
106未知的签名方法Unsupported signature method
107timestamp参数无效Invalid/Used timestamp parameter
109无效的用户资料字段名Invalid user info field
110无效的access tokenAccess token invalid or no longer valid
111access token过期Access token expired
210用户不可见User not visible
211获取未授权的字段Unsupported permission
212没有权限获取用户的emailNo permission to access user email
800未知的存储操作错误Unknown data store API error
801无效的操作方法Invalid operation
802数据存储空间已超过设定的上限Data store allowable quota was exceeded
803指定的对象不存在Specified object cannot be found
804指定的对象已存在Specified object already exists
805数据库操作出错,请重试A database error occurred. Please try again
900访问的应用不存在No such application exists

异常处理设计文档

1、在Controller层统一捕获异常

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0pleq66Q-1635754163695)(D:\大数据\笔记\孤尽\T31.assets\image-20211101153800619.png)]

2、全局异常处理组件GlobalExceptionHandler 的定义和使用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Koq11W4e-1635754163696)(D:\大数据\笔记\孤尽\T31.assets\image-20211101153818178.png)]

3、API层异常设计实践

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jK1TKGbE-1635754163697)(D:\大数据\笔记\孤尽\T31.assets\image-20211101153829115.png)]

4、Service层异常设计实

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IlJDBN2T-1635754163697)(D:\大数据\笔记\孤尽\T31.assets\image-20211101153903878.png)]

5、DAO数据处理层异常、日志实践

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6xm25Xpf-1635754163698)(D:\大数据\笔记\孤尽\T31.assets\image-20211101153925106.png)]

6、使用MDC实现轻量级调用链路追踪

分布式链路追踪

将一次分布式请求还原成调用链路,将一次分布式请求的调用 情况集中展示,比如各个服务节点上的耗时、请求具体到达哪 台机器上、每个服务节点的请求状态等等

链路追踪的主要功能

  • 故障快速定位
  • 链路性能可视化
  • 链路分析

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-REGFoRtV-1635754163698)(D:\大数据\笔记\孤尽\T31.assets\image-20211101153946485.png)]

6、使用MDC实现轻量级调用链路追踪

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oQaH10zi-1635754163699)(D:\大数据\笔记\孤尽\T31.assets\image-20211101154112792.png)]

7、用有限的异常类处理业务中复杂多变的无限可能

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yQKOtIEO-1635754163700)(D:\大数据\笔记\孤尽\T31.assets\image-20211101154129400.png)]

8、降低系统的维护难度与过度设计、冗余的手段

通用模块的单独拆分

合理的领域划分

合适的工程结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JhK3yY0k-1635754163700)(D:\大数据\笔记\孤尽\T31.assets\image-20211101154159342.png)]

T31异常处理设计文档
1、防止 NPE(空指针异常,Java中常见难处理的异常)

NPE 产生的场景:

1)返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。

例如:

public int f() 
{ return Integer 对象}

如果为 null,自动解箱抛 NPE。 数据库的查询结果可能为 null。

特殊NPE场景及其处理对策,级联调用时容易产生NPE

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8rCqryfx-1635754163701)(D:\大数据\笔记\孤尽\T31.assets\image-20211101155510827.png)]

3) 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。

4) 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。

5) 对于 Session 中获取的数据,建议 NPE 检查,避免空指针。

6) 级联调用,如下

obj.getA().getB().getC();

这样的一连串调用,易产生 NPE。

我们使用 JDK8 的 Optional 类来防止 NPE 问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jxskds82-1635754163702)(D:\大数据\笔记\孤尽\T31.assets\image-20211101155636854.png)]

2、RuntimeException

RuntimeException 可以通过预先检查进行规避,而不应该 通过 catch 来处理,

例如:

IndexOutOfBoundsException,NullPointerException
能判断一定要判断,不要无脑使用try catch
if (obj != null) {...}

这样的 try catch 没有意义
try { obj.method() } catch (NullPointerException e) {...}

3、finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。 如果 JDK7 及以上,可以使用 try-with-resources 方式,向我们现在开发常用的Java8就可以用这种方法。

4、不能在 finally 块中使用 return,finally 块中的 return 返回后方法结束执行,不 会再执行 try 块中的 return 语句。

5、foreach遍历集合的异常

  1. 不要在foreach循环里进行元素的remove/add操作
  2. foreach循环会自动跳过遍历空集合,如果对于有null值的集合,碰到null时需要注意NPE
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐