热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

java事务异常处理_controller的异常处理以及service层的事务控制controller层处理异常还是service处理异常PS下...

JavaWeb中Service层异常抛到Controller层处理还是直接处理?大家一般在项目里,业务层的方法报错都会直接往上抛到控制层来做统一的处理&

JavaWeb 中 Service 层异常抛到 Controller 层处理还是直接处理?大家一般在项目里,业务层的方法报错都会直接往上抛到控制层来做统一的处理,一般来说,有经验的开发者会选择这样的异常处理方法吗?如果不这样做,那么一般怎样做?下面我们一起来学习下。

一般初学者学习编码和错误处理时,先知道编程语言有一种处理错误的形式或者约定(如Java就是抛异常),然后就开始用这些工具,但是却反过来忽视这个问题的本质:

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

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

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

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

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

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

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

我们可以推广下,一个工作线程的“外部容器“是管理工作线程的“master”。一个网络请求的“外部容器”是一个web server。一个用户进程的“外部容器”是操作系统。Erlang把这种supervisor-worker的机制融入到语言的设计中。

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

请求来自于前端,对于因为用户请求有误(数据合法性、权限、用户上下文状态)造成的问题,最终大概率只能告诉用户。因此抛异常到一个集中处理错误的地方,把异常转换为某个业务错误码的方法是合理的。

后端服务一般都是无状态的。这也是互联网系统设计的一般性原则。无状态就意味着可以随意重启。对于用户的数据因为下一条一般情况下不会出问题。

后端对数据的修改依赖DB的事务。因此一个改了一半的没提交的事务是不会造成副副作用。

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

42c371fcaa1ea4efc6c48250b225c817.png

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

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

4bdceb049504b8c7930ffdc582736892.png

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

a6709fa6b30caf13a8d425eaadfe91cd.png

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

2a5d1e4f7d22e7ed1708ca08e562bb65.png

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

总结一下,我希望所有程序员对错误处理都要有起码的敬畏之心。Java这边因为Checked Exception的设计问题不得不避免使用,而Uncaughted Exception实在是太过于弱鸡,是不能给程序员提供更好地帮助的。

因此,程序员在每次抛错或者处理错误的时候都要对自己灵魂三击:这个错误的处理是正确的吗?会让用户看到什么?会不会搞乱数据?不要以为自己抛了个异常就不管了。

此外,在编译器不能帮上太多忙的时候,好好写UT来保护代码脆弱的正确性。



推荐阅读
  • 本文详细介绍了Java代码分层的基本概念和常见分层模式,特别是MVC模式。同时探讨了不同项目需求下的分层策略,帮助读者更好地理解和应用Java分层思想。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • Spring框架的核心组件与架构解析 ... [详细]
  • 深入解析Struts、Spring与Hibernate三大框架的面试要点与技巧 ... [详细]
  • DAO(Data Access Object)模式是一种用于抽象和封装所有对数据库或其他持久化机制访问的方法,它通过提供一个统一的接口来隐藏底层数据访问的复杂性。 ... [详细]
  • Spring Boot 中配置全局文件上传路径并实现文件上传功能
    本文介绍如何在 Spring Boot 项目中配置全局文件上传路径,并通过读取配置项实现文件上传功能。通过这种方式,可以更好地管理和维护文件路径。 ... [详细]
  • 本文介绍了如何利用HTTP隧道技术在受限网络环境中绕过IDS和防火墙等安全设备,实现RDP端口的暴力破解攻击。文章详细描述了部署过程、攻击实施及流量分析,旨在提升网络安全意识。 ... [详细]
  • 第二十五天接口、多态
    1.java是面向对象的语言。设计模式:接口接口类是从java里衍生出来的,不是python原生支持的主要用于继承里多继承抽象类是python原生支持的主要用于继承里的单继承但是接 ... [详细]
  • Python应用实例大揭秘:七大令人惊叹的高阶技巧展示
    2020年,Python无疑成为了最炙手可热的编程语言,其影响力已远远超出程序员的范畴。从初学者到资深从业者,甚至小学生,都在纷纷加入Python的学习热潮中。凭借其低门槛、易上手和强大的功能,Python正逐渐成为各行业不可或缺的工具。本文将揭示七个令人惊叹的Python高级应用技巧,帮助读者进一步提升编程水平。 ... [详细]
  • 基于Net Core 3.0与Web API的前后端分离开发:Vue.js在前端的应用
    本文介绍了如何使用Net Core 3.0和Web API进行前后端分离开发,并重点探讨了Vue.js在前端的应用。后端采用MySQL数据库和EF Core框架进行数据操作,开发环境为Windows 10和Visual Studio 2019,MySQL服务器版本为8.0.16。文章详细描述了API项目的创建过程、启动步骤以及必要的插件安装,为开发者提供了一套完整的开发指南。 ... [详细]
  • 本文详细探讨了几种常用的Java后端开发框架组合及其具体应用场景。通过对比分析Spring Boot、MyBatis、Hibernate等框架的特点和优势,结合实际项目需求,为开发者提供了选择合适框架组合的参考依据。同时,文章还介绍了这些框架在微服务架构中的应用,帮助读者更好地理解和运用这些技术。 ... [详细]
  • 优化后的标题:深入探讨网关安全:将微服务升级为OAuth2资源服务器的最佳实践
    本文深入探讨了如何将微服务升级为OAuth2资源服务器,以订单服务为例,详细介绍了在POM文件中添加 `spring-cloud-starter-oauth2` 依赖,并配置Spring Security以实现对微服务的保护。通过这一过程,不仅增强了系统的安全性,还提高了资源访问的可控性和灵活性。文章还讨论了最佳实践,包括如何配置OAuth2客户端和资源服务器,以及如何处理常见的安全问题和错误。 ... [详细]
  • V8不仅是一款著名的八缸发动机,广泛应用于道奇Charger、宾利Continental GT和BossHoss摩托车中。自2008年以来,作为Chromium项目的一部分,V8 JavaScript引擎在性能优化和技术创新方面取得了显著进展。该引擎通过先进的编译技术和高效的垃圾回收机制,显著提升了JavaScript的执行效率,为现代Web应用提供了强大的支持。持续的优化和创新使得V8在处理复杂计算和大规模数据时表现更加出色,成为众多开发者和企业的首选。 ... [详细]
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • OpenAI首席执行官Sam Altman展望:人工智能的未来发展方向与挑战
    OpenAI首席执行官Sam Altman展望:人工智能的未来发展方向与挑战 ... [详细]
author-avatar
mobiledu2402851203
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有