之前我们业务团队在处理全局异常时,在每个业务微服务中都加入了@restcontrolleradvice+@exceptionhandler来进行全局异常捕获。某次领导在走查代码的时候,就提出了一个问题,为什么要每个微服务项目都要自己在写一套全局异常代码,为什么不把全局异常块秋老虎抽成一个公共的jar,然后每个微服务以jar的形式引入。后面业务团队就根据领导的要求,把全局异常块单独抽离出来封装成jar。今天聊的话题就是关于把全局异常抽离出来,发生的一些问题
之前团队的业务错误码定义是:业务服务前缀 + 业务模块 + 错误码,如果是识别不了的异常,则使用业务前缀 + 固定模块码 + 固定错误码。
之前的全局异常伪代码如下
@restcontrolleradvice@slf4jpublic class globalexceptionbahandler { @exceptionhandler(exception.class) @responstatus(httpstatus.internal_rver_error) public ajaxresult handleexception(exception e) { string rviceprifix =牙膏品牌 "u"; string moudlecode = "001"; string code = "0001"; string errorcode = rviceprifix + moudlecode + code; string msg = e.getmessage(); if(stringutils.impty(msg)){ msg = "服务端异常"; } log.error(msg, e); return ajaxresult.error(msg, errorcode); } }
现在全局异常抽离出来后,那个业务服务前缀如何识别?之前未抽离时,业务服务前缀各个业务服务直接写死在代码里。
当时我们临时的解决方案是通过spring.application.name来解决。因为全局异常代码块抽离出来后,最终还是要expensive的比较级和最高级被服务引入的。因此获取业务服务前缀的伪代码可以通过如下获取
public enum rviceprefixenum { ur_rvice("u","用户中心"); private final string rviceprefix; private final string rvicedesc; rviceprefixenum(string rviceprefix,string rvicedesc) { this.rviceprefix = rviceprefix; this.rvicedesc = rvicedesc; } public string getrviceprefix() { return rviceprefix; } public string getrvicedesc() { return rvicedesc; }}
public string getrviceprefix(@value("${spring.application.name}") string rvicename){ return rviceprefixenum.valueof(rvicename).getrviceprefix(); }
但这种方案其实是存在弊端
弊端一:通过枚举硬编码,预设了目前了微服务名称,一旦项目改变了微服务名,就找不到服务前缀了。
弊端二:如果新上线了业务服务模块,这个枚举类还得改动
后面我们在全局异常jar中增加了自定义业务码的配置,业务人员仅需在springboot配置文件配置,形如下
lybgeek: bizcode: prefix: u
此时全局异常改造示例形如下
@restcontrolleradvice@slf4jpublic class globalexceptionbahandler { @autowired private rvicecodeproperties rvicecodeproperties; @exceptionhandler(exception.class) @responstatus(httpstatus.internal_rver_error) public ajaxresult handleexception(exception e) { string rviceprifix = rvicecodeproperties.getprifix(); string moudlecode = "001"; string code = "0001"; string errorcode = rviceprifix + moudlecode + code; string msg = e.getmessage(); if(stringutils.impty(msg)){ msg = "服务端异常"; } log.error(msg, e); return ajaxresult.error(msg, errorcode); }}
如果全局异常直接如下写,是不存在问题。示例如下
@restcontrolleradvice@slf4jpublic class globalexceptionbahandler { @autowired private rvicecodeproperties rvicecodeproperties; @exceptionhandler(exception.class) @responstatus(httpstatus.internal_rver_error) public ajaxresult handleexception(exception e) { string rviceprifix = rvicecodeproperties.getprifix(); string moudlecode = "001"; string code = "0001"; string errorcode = rviceprifix + moudlecode + code; string msg = e.getmessage(); if(stringutils.impty(msg)){ msg = "服务端异常"; } log.error(msg, e); return ajaxresult.error(msg, httpstatus.internal_rver_error.value()); } @exceptionhandler(bizexception.class) public ajaxresult handleexception(bizexception e) { return ajaxresult.error(e.getmessage(), e.geterrorcode()); }}
即全局异常直接分为业务异常和execption这两种,这样划分的弊端在于没办法细分异常,而且也使项目组定义的模块码和业务码没法细分。因此我们也列出常用可以预知的系统异常,示例如下
/** *参数验证失败 * @param e * @return */ @exceptionhandler(constraintviolationexception.class) @responstatus(httpstatus.bad_request) public ajaxresult handleexception(constraintviolationexception e) { log.error("参数验证失败", e); return ajaxresult.error("参数验证失败", httpstatus.bad_request.value()); } /** * 数据库异常 * @param e * @return */ @exceptionhandler({sqlexception.class, mybatispluxception.class, mybatissystemexception.class, org.apache.ibatis.exceptions.persistenceexception.class, badsqlgrammarexception.class }) @responstatus(httpstatus.bad_request) public ajaxresult dbexception(exception e) { string msg = exceptionutil.getexceptionmessage(e); log.error(msg, e); return ajaxresult.error(msg,httpstatus.bad_request.value()); } /** * 数据库中已存在该记录 * @param e * @return */ @exceptionhandler(duplicatekeyexception.class) @responstatus(httpstatus.conflict) public ajaxresult handleexception(duplicatekeyexception e) { log.error("数据库中已存在该记录", e); return ajaxresult.error("数据库中已存在该记录", httpstatus.conflict.value()); }
不过这样导致了一个问题,就是全局异常和业务方使用相同的依赖jar,但存在版本差异时,可能就会存在依赖冲突,导致月经期不能吃什么业务项目启动报错。因此解决方案就是在pom文件加入optional标签。示例如下
<dependency> <groupid>com.baomidou</groupid> <artifactid>mybatis-plus-boot-starter</artifactid> <optional>true</optional> </dependency>
这标签的意思这jar坐标是可选的,因此如果项目中已经有引入该jar的坐标,就直接用该jar的坐标
这个问题的产生:举个示例,我们的业务微服务项目有聚合层,某些聚合层是不需要依赖存储介质,比如mysql。因此这些聚合层项目pom就不会引入类似mybatis相关的依赖。但我们的全局异常又需要类似mybatis相关的依赖,这样导致如果要引用全局异常模块,有得额外加入业务方不需要的jar。
因此springboot的条件注解就派上用场了,利用@conditionalonclass注解。示例如下
@restcontrolleradvice@slf4j@conditionalonclass({sqlexception.class, mybatispluxception.class, mybatissystemexception.class, org.apache.ibatis.exceptions.persistenceexception.class, badsqlgrammarexception.class, duplicatekeyexception.class})public class globalexceptiondbhandler { /** * 数据库异常 * @param e * @return */ @exceptionhandler({sqlexception.class, mybatispluxception.class, mybatissystemexception.class, org.apache.ibatis.exceptions地球的内部结构.persistenceexception.class, badsqlgrammarexception.class }) @responstatus(httpstatus.bad_request) public ajaxresult dbexception(exception e) { string msg = exceptionutil.getexceptionmessage(e); log.error(msg, e); return ajaxresult.error(msg,httpstatus.bad_request.value()); } /** * 数据库中已存在该记录 * @param e * @return */ @exceptionhandler(duplicatekeyexception.class) @responstatus(httpstatus.conflict) public ajaxresult handleexception(duplicatekeyexception e) { log.error("数据库中已存在该记录", e); return ajaxresult.error("数据库中已存在该记录", httpstatus.conflict.value()); }}
@conditionalonclass这个注解的作用就是如果classpath存在指定的类,则该注解上的类会生效。
同时这边有个细节点,就是全局异常可能就得细分,即把原来的大一统的全局异常,按业务场景分开,比如存储介质相关的存储异常,web相关异常
本文主要讲当将全局异常抽离成jar,可能会发生的问题。这边有涉及到一些细节点没讲,比如为啥要定义服务前缀+业务模块码+错误码,其实主要还是为了好排查问题。
也许有朋友会问,你们都搞了微服务,难道不上分布式链路追踪?根据分布式链路追踪可以很方便定位到整个链路了。但真的开发微服务的时候,如果公司原来就就没运维平台,有时候为了成本考量,测试、开发环境都不会上的分布式链路追踪的,甚至线上项目初期也不会上分布式链路追踪。因此定义好相关的业务码就变得格外重要
到此这篇关于springboot项目全局异常处理那些事儿的文章就介绍到这了,更多相关springboot项目全局异常处理内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-exception
本文发布于:2023-04-04 16:53:03,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/zuowen/b12160792d674288a6463778edd102e3.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:深入聊一聊springboot项目全局异常处理那些事儿.doc
本文 PDF 下载地址:深入聊一聊springboot项目全局异常处理那些事儿.pdf
留言与评论(共有 0 条评论) |