文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Java中Service层异常该怎么处理

2023-06-27 10:35

关注

这篇文章主要介绍“Java中Service层异常该怎么处理”,在日常操作中,相信很多人在Java中Service层异常该怎么处理问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java中Service层异常该怎么处理”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

处理错误是为了写出正确的程序

到底怎么算“正确”呢?是要由解决的问题决定的。问题不同,解决方案就不同。

比如,一个web接口接受用户的请求,这个请求需要传入一个参数“年龄”,也许业务要求这个字段应该是个0~150之间的整数。如果用户输入的是个字符串或者负数就肯定不被接受。一般在后端的某个地方都会做输入的合法性检查,检查不过就抛异常。但是归根到底这个问题的“正确”解决方法总是要以某种形式提示用户。而提示用户是某种前端工作,这就要看这个界面到底是appH5 + ajax还是类似于jsp那样的服务器产生的界面。不管哪种,你需要根据需求去”设计一个修复错误“的流程。比如一个常见的流程需要后端抛异常,然后一路到某个集中处理错误的代码,将其转换为某个HTTP的错误(某个特定业务错误码)提供给前端,前端再去做”提示“。如果用户输入了非法的请求,从逻辑上后端都没法自己修复,这是个“正确”的策略。

换一个例子,比如用户想上传一个头像,后端将图片发给某个云存储,结果云存储报500错误。怎么办呢?你可能想到了重试几次,因为也许问题仅仅是临时的网络抖动而已,重试就可以正常执行。但如果重试多次无效。如果做系统时设计了某种热备方案,那么就可能改为发到另外一个服务器上。“重试”和“使用备份的依赖”都是“立刻处理“。

但如果重试无效,所有的备份服务也无效,那么也许就能像上面那样把错误抛给前端,提示用户“服务器开小差”。从这个方案很容易看出来,你想把错误抛到哪里是因为那个catch的地方是处理问题最方便的地方。一个问题的解决方案可能要几个不同的错误处理组合起来才能办到。

另外一个例子,你的程序抛了一个NPE。这一般就是程序员的bug——要不就是程序员想要表达一个东西”没有“,结果在后续处理中忘了判断是否为null;要不就是在写代码时觉得100%不可能为null的地方出现了一个null。不管哪种情况,这个错误用户总会看到一个很含糊的报错信息,这远远不够。“正确”的办法是程序员自己能尽快发现它,并尽快修复。要做到这一点,需要监控系统不断的爬log,把问题报警出来。而不是等到用户找客服来吐槽。

再换一个例子,比如你的后端程序突然OOM,挂了。挂的程序是没法恢复自己的。要做到“正确”就必须得在服务之外的容器考虑这个问题。比如你的服务跑在k8s上,他们会监控你程序的状态,然后重新启动新的服务实例以弥补挂掉的服务,还得调整流量,把去往挂掉服务的流量切掉,重新换到新的实例上。这里的恢复因为跨系统所以不能仅仅用异常实现,但是道理是一样的。但光靠重启就是“正确”的吗?如果服务是完全无状态的,问题不大。但是如果是有状态的,部分用户数据可能就会被执行一半的请求搞乱套。因此重启时要留意先“恢复数据到合法状态”。这又回到了你需要知道怎么样才是“正确”的做法。只依靠简单的语法功能是不能无脑解决这个事的。

(推荐微课:Java微课)

Web程序之所以很大程度上能够把异常抛给顶层,主要由于3个原因:

但你要清楚上面这3条并不是总是成立的。总会存在一些处理逻辑并非完全无状态,也并不是所有的数据修改都能用一个事务保护。尤其要注意对微服务的调用,对内存状态的修改是没有事务保护的,一不留神就会出现搞乱用户数据的问题。比如:

 try {   int res1 = doStep1();   this.statusVar1 += res1;   int res2 = doStep2();   this.statusVar2 += res2;   int res3 = doStep3(); // throw an exception   this.statusVar3 = statusVar1 + statusVar2 + res3;} catch ( ...) {    // ...}

先假设this.statusVar1, this.statusVar2, this.statusVar3之间需要维护某种不变的约束(invariant)。然后执行这段代码时,如果在doStep3那抛出一个异常下面对statusVar3的赋值就不会执行。这时如果不能将statusVar1statusVar2的修改rollback回去,就会造成数据违反约束的问题。而程序员一般是很难直接发现这个数据被改坏了。而坏掉的数据可能会偷偷的导致其他依赖这个数据的代码逻辑出错(比如原本应该给积分的,结果却没给)。而这种错误一般非常难调查,从大量数据里找到不正确的那一小撮是相当困难的事。

比起上面这段更难搞得定的是这样的代码:

// controllervoid controllerMethod() {  try {    return svc.doWorkAndGetResult();  } catch (Exception e) {    return ErrorJsonObject.of(e);  }}// class svcvoid doWorkAndGetResult() {    int res1 = otherSvc1.doStep1();    this.statusVar1 += res1;    int res2 = otherSvc2.doStep2();    this.statusVar2 += res2;    int res3 = otherSvc3.doStep3();    this.statusVar3 = statusVar1 + statusVar2 + res3;    return SomeResult.of(this.statusVar1, this.statusVar2, this.statusVar3);}

这段代码的可怕之处在于,你在写的时候可能会以为doStep1~3这种东西即使抛异常,也能被Controller里的catch。在svc这层是不用处理任何异常的,因此不写try……catch是天经地义的。但实际上doStep1doStep2doStep3任何一个抛异常都会造成svc的数据状态不一致。甚至你一开始都可以通过文档或者其他沟通方式确定doStep1doStep2doStep3一开始都是必然可以成功,不会抛错的,因此你写的代码一开始是对的。但是你可能无法控制他们的实现(比如他们是另外一个团队开发的lib提供的),而他们的实现可能会改成会抛错。你的代码可能在完全不自知的情况下从“不会出问题”变成了“可能出问题”…… 更可怕的是类似于这样的代码是不能正确工作的:

void doWorkAndGetResult() {    try {       int res1 = otherSvc1.doStep1();       this.statusVar1 += res1;       int res2 = otherSvc2.doStep2();       this.statusVar2 += res2;       int res3 = otherSvc3.doStep3();       this.statusVar3 = statusVar1 + statusVar2 + res3;       return SomeResult.of(this.statusVar1, this.statusVar2, this.statusVar3);   } catch (Exception e) {     // do rollback   }}

你可能以为这样就会处理好数据rollback了,甚至你会觉得这种代码非常优雅。但是实际上doStep1~3每一个地方抛错,rollback的代码都不一样。你必须得这么写:

void doWorkAndGetResult() {    int res1, res2, res3;    try {       res1 = otherSvc1.doStep1();       this.statusVar1 += res1;    } catch (Exception e) {       throw e;    }    try {      res2 = otherSvc2.doStep2();      this.statusVar2 += res2;    } catch (Exception e) {      // rollback statusVar1      this.statusVar1 -= res1;      throw e;    }      try {      res3 = otherSvc3.doStep3();      this.statusVar3 = statusVar1 + statusVar2 + res3;    } catch (Exception e) {      // rollback statusVar1 & statusVar2      this.statusVar1 -= res1;      this.statusVar2 -= res2;      throw e;   } }

这才是能得到正确结果的代码——在任何地方出现错误都能维护数据一致性。优雅吗?看起来很丑。这甚至比goif err != nil还丑。但如果一定要在正确性和优雅性上作出取舍,我会毫不犹豫的选择前者。作为程序员是不能直接认为抛异常可以解决任何问题的,你必须学会写出有正确逻辑的程序,哪怕很难,并且看起来很丑。为了达成很高的正确性,你不能总是把自己大部分注意力放在“一切都OK的流程上“,而把错误看作是可以随便搞一下的工作,或者简单的相信exception可以自动搞定一切。

到此,关于“Java中Service层异常该怎么处理”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯