在开发业务系统中,我们目前绝大多数采用MVC模式,但是往往有人把service跟controller紧紧的耦合在一起,甚至直接使用Threadlocal来隐式传值,并且复杂的逻辑几乎只能使用service中存储的全局对象来传递处理结果,包括异常。

这样一来首先有违MVC模式,二来逻辑十分不清晰,难以维护.本文结合工作经验,给出一些异常使用建议,使用spring来实战异常为我们带来的好处。

常常,我们读罢了各种java的书,异常的各种机制,特性都很清楚,但是始终还是不知道如何使用,甚至背下了概念,却不知道如何致用。

我们开发的业务系统,或者是产品,常常面临着这样的问题:

  • 系统运行出错,但是完全不知道错误发生的位置。

  • 我们找到了错误的位置,但是完全不知道是因为什么。

  • 系统明明出了错误,但是就是看不到错误堆栈信息。

什么情况需要自定义异常

经常看到一些项目,在全局定义一个 AppException,然后所有地方都只抛出这个异常,并且把捕获的异常case到这个AppException中。会有如下问题:

  • 浪费log日志存储空间,并且栈顶并不是最接近发生异常的代码位置。

  • 只有一种异常类,无法精准区分开异常类型。

  • 异常类后期难以修改以增加其携带的信息。

什么情况需要手动处理异常

我不会把书上的东西直接复制下来,这里说一下容易记住的,并且适合业务开发的。

  • 你有能力处理异常,并且你知道如何处理

  • 你有责任处理异常

自定义业务异常

考虑如下场景:系统提供一个API,用于修改用户信息,服务器端采用json数据交互.首先我们定义ServiceException,用来表示业务逻辑受理失败,它仅表示我们处理业务的时候发现无法继续执行下去。

接下来看下Controller层.

关于上述Controller写法乍一看会有一些冗余,如果无法理解,请仔细研读MVC设计模式. 先不管service,我们来考虑下。 一个业务系统不可能不对用户提交的数据进行验证,验证包括两方面:有效性和合法性

  • 有效性: 比如用户所在岗位,是否属于数据库有记录的岗位ID,如果不存在,无效.

  • 合法性: 比如用户名只允许输入最多12个字符,用户提交了20个字符,不合法.

有效性检查,可以交给java的校验框架执行,比如JSR303。假设用户提交的数据经过验证都合法,还是有一些情况是不能调用修改逻辑的。

  1. 要修改的用户ID不存在.

  2. 用户被锁定,不允许修改.

  3. 乐观锁机制发现用户已经被被人修改过.

  4. 由于某种原因,我们的程序无法保存到数据库.

  5. 一些程序员错误的开发了代码,导致保存过程中出现异常,比如NPE.

对于前3种,我们认为是有效性检查失败,第4种属与我们无法处理的异常,第5种就是程序员bug。

现在的问题是,前三种情况我们如何通知用户呢?

  1. 在ccontroller 调用userService的checkUserExist()方法.

  2. 在controller直接书写业务逻辑.

  3. 在service响应一个状态码机制,比如1 2 3表示错误信息,0 表示没有任何错误.

显然前2种方法都不可取,因为MVC不设计模式告诉我们,controller是用来接收页面参数,并且调用逻辑处理,最后组织页面响应的地方。我们不可以在controller进行逻辑处理,controller只应该负责用户API入口和响应的处理(如若不然,思考一下如果有一天service的代码打包成jar放到另一个平台,没有controller了,该怎么办?)

状态码机制是个不错的选择,可是如此一来,用户保存逻辑变了,比如增加一个情况,不允许修改已经离职的用户,那么我们还需要修改controller的代码,代码量增加,维护成本增高,并且还耦合了service,不符合MVC设计模式。

那么怎么办呢?现在我们来看下service代码如何编写

这样一来只要我们检查到不允许保存的项目,我们就可以直接throw 一个新的异常,异常机制会帮助我们中断代码执行。

接下来有2种选择:

  1. 在controller 使用try-catch进行处理.

  2. 直接把异常抛给上层框架统一处理.

第1种方式是不可取的,注意我们抛出的ServiceException,它仅仅逻辑处理异常,并且我们的方法前面没有声明throws ServiceException,这表示他是一个非受查异常.controller也没有关心会发生什么异常。

为什么不定义成受查异常呢?如果是一个受查异常,那么意味着controller必须要处理你的异常。并且如果有一天你的业务逻辑变了,可能多一种检查项,就需要增加一个异常,反之需要删除一个异常,那么你的方法签名也需要改变,controller也随之要改变,这又变成了紧耦合,这和用状态码123表示处理结果没有什么不同。

我们可以为每一种检查项定义一个异常吗?可以,但是那样显得太多余了。因为业务逻辑处理失败的时候,根据我们需求,我们只需要通知用户失败的原因(通常应该是一段字符串),以及服务器受理失败的一个状态码(有时可能不需要状态码,这要看你的设计了),这样这需要一个包含原因属性的异常即可满足我们需求。

最后我们决定这个异常继承自RuntimeException。并且包含一个接受一个错误原因的构造器,这样controller层也不需要知道异常,只要全局捕获到ServiceException做统一的处理即可,这无论是在struct1,2时代,还是springMVC中,甚至servlet年代,都是极为容易的!

异常不提供无参构造器,因为绝对不允许你抛出一个逻辑处理异常,但是不指明原因,想想看,你是必须要告诉用户为什么受理失败的!

如此一来,我们只需要全局统一处理下 ServiceException 就可以了,很好,spring为我们提供了ControllerAdvice机制,有关ControllerAdvice,可以查阅springMVC使用文档,下面是一个简单的示例:

在这个时候,我们就可以很轻松的处理各种情况了.

注意一点,在这个类中,我们定义了2个log对象,分别指向 ServiceException.class 和 ModuleControllerAdvice.class。并且处理 ServiceException的时候使用了info级别的日志输出,这是很有用的。

  • 首先,ServiceException一定要和其他的代码错误分离,不应该混为一谈.

  • 其次,ServiceException并不一定要记录日志,我们应该提供独立的log对象,方便开关.

接下来你可以在修改用户的时候想客户端响应这样的JSON

如此一来没有任何地方需要关心异常,或者业务逻辑校验失败的情况。用户也可以得到很友好的错误提示。

如何对异常进行分类

如果你只需要一句概括,那么直接定义一个简单的异常,用于中断处理,并且与用户保持友好交互即可。

如果不可能一句话描述清楚,并且包含附加信息,比如需要在日志或者数据库记录消息ID,此时可能专门针对这种重要/复杂业务创建独立异常。

上述两种情况因为web系统,是用户发起请求之后需要等待程序给予响应结果的。

如果是后台作业,或者复杂业务需要追溯性。这种通常用流程判断语句控制,要用异常处理。我们认为这些流程判断一定在一个原子性处理中。并且检查到(不是遇到)的问题(不是异常)需要记录到用户可友好查看的日志。这种情况属于处理反馈,并不叫异常。

综上,笔者通常分为如下几类:

  1. 逻辑异常,这类异常用于描述业务无法按照预期的情况处理下去,属于用户制造的意外。

  2. 代码错误,这类异常用于描述开发的代码错误,例如NPE,ILLARG,都属于程序员制造的BUG。

  3. 专有异常,多用于特定业务场景,用于描述指定作业出现意外情况无法预先处理。

各类异常必须要有单独的日志记录,或者分级,分类可管理。有的时候仅仅想给三方运维看到逻辑异常。

注意

异常设计的初衷是解决程序运行中的各种意外情况,且异常的处理效率比条件判断方式要低很多。

上面这句话出自<java编程思想>,但是我们思考如下几点:

业务逻辑检查,也是意外情况

UnknownHostException,表示找不到这样的主机,这个异常和NoUserException有什么区别么?换言之,没有这样的主机是异常,没有这样的用户不是异常了么?所以一定要弄明白什么是用异常来控制逻辑,什么是定义程序异常。

异常处理效率很低

书中所示的例子,是在循环中大量使用try-catch进行检查,但是业务系统,用户发起请求的次数与该场景天壤地别。淘宝的11`11是个很好的反例。但是请你的系统上到这个级别再考虑这种问题。

  1. 系统有千万并发,不可能还去考虑这些中规中矩的按部就班的方式,别忘了MVC本来就浪费很多资源,代码量增加很多。

  2. 业务系统也存在很多巨量任务处理的情况。但是那些任务都是原子性的,现在MVC中的controller和service可不是原子性的,不然为什么要区分这么多层呢。

  3. 如果那么在乎效率,考虑下重写Throwable的fillStackTrace方法。你要知道异常的开销大到底大在什么地方,fillStackTrace是一个native方法,会填充异常类内部的运行轨迹。

不要用异常进行业务逻辑处理

我们先来看一个例子:

上述代码就是典型的使用异常来处理业务逻辑。这种方式需要严重的禁止!上述代码最大的问题在于,我们如何利用异常来自动处理事务呢?

然而这和我们的异常中断service没有什么冲突.也并不是一回事.

  • 我们提倡在 业务处理 的时候,如果发现无法处理直接抛出异常即可。

  • 而并不是在 逻辑处理 的时候,用异常来判断逻辑进行的状况。

改正后的逻辑

如何处理java异常相关推荐

  1. 如何处理Java异常及常见异常

    六种异常处理的陋习 你觉得自己是一个Java专家吗?是否肯定自己已经全面掌握了Java的异常处理机制?在下面这段代码中,你能够迅速找出异常处理的六个问题吗? 1 OutputStreamWriter ...

  2. java异常大全,如何处理异常,如何自定义异常

    此文章截图转载自:java异常大全!如何处理异常!如何自定义异常!_qq1328585964的博客-CSDN博客_java 异常大全s 我怕文章莫名消失所以转载下!!!我收藏的文章偶尔会没有

  3. Java异常详解及如何处理

    来源:Java异常详解及如何处理 简介 程序运行时,发生的不被期望的事件,它阻止了程序按照程序员的预期正常执行,这就是异常.异常发生时,是任程序自生自灭,立刻退出终止,还是输出错误给用户?或者用C语言 ...

  4. 一篇文章带你认识 Java 异常以及如何处理异常

    异常,是 Java 中非常常用的功能,它可以简化代码,并且增强代码的安全性.但是往往在开发时遇到需要抛出的异常,我们也仅仅是写一个 Throw 语句或者使用框架提供的抛异常方法去一笔带过,没有进行深入 ...

  5. java build path entries 为空_同事的代码简直没法看,我来教你如何更优雅的设计Java异常...

    点击上方蓝色字体,选择"设为星标" 回复"666"获取面试宝典 异常处理是程序开发中必不可少操作之一,但如何正确优雅的对异常进行处理确是一门学问,笔者根据自己的 ...

  6. 教你如何更优雅的设计Java异常

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 来源:lrwinx https://lrwinx.github.i ...

  7. 如何优雅的设计java异常

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 来源:lrwinx https://lrwinx.github.i ...

  8. 【Java挠头】Java异常、捕获、处理、throw、throws等绝妙剖析

    –本文摘要–     1.本文的目标读者:是接触过Java异常,但是仅限于书本和一些例题,某一天写着写着会突然觉得"'异常'没什么意义,Java不是已经帮我们处理了嘛?"的这些朋友 ...

  9. Java异常之异常机制

    2019独角兽企业重金招聘Python工程师标准>>> Java异常处理机制依赖5个关键字try,catch,finally,throw,throws.try关键字后跟着可能出现异常 ...

最新文章

  1. 一文了解神经网络的基本原理
  2. ES transport client使用
  3. jQuery学习笔记:Ajax(二)
  4. matlab仿真习题,(MATlab仿真部分习题答案.doc
  5. python35个关键字_Python关键字35个
  6. 人人开源需要的问题与解决方案(一)——公网访问、内网穿透
  7. u盘中病毒文件夹变成.exe解决
  8. 两个for循环写出大小写字母表
  9. python3计算md5_python 计算文件的md5值实例
  10. 实践练习二:手动部署 OceanBase 集群
  11. 工具技巧和读文档 | 读函数式编程接口文档 | 匿名内部类 | lambda表达式 |IDEA
  12. 通俗的讲,网络爬虫到底是什么?
  13. 3055. 字符频率
  14. HDU 2036改革春风吹满地(有向面积的介绍)
  15. PyQT5 (二十五) 绘图API: 绘制不同类型的直线 的案例drawLine()
  16. 根据经纬度求遥感影像行列号 IDL 编程
  17. 基于SSM车辆维修管理系统
  18. 基于典型相关分析(CCA)的多元变化检测算法(MAD)
  19. 在中兴新支点操作系统上可以运行QQ啦!
  20. vmware操作linux,linux虚拟机初步 vmware的简单操作【鸟哥的私房菜第五章】

热门文章

  1. 经典书摘:基于信用本质,区块链应用的9大场景
  2. 质数乘积(大数乘法+埃氏筛法)
  3. 多级列表为“第一章”下产生含“阿拉伯数字章节号”的题注(word题注自定义)
  4. JAVA计算机毕业设计桌游店会员管理系统Mybatis+系统+数据库+调试部署
  5. phpMyAdmin无法缓存模板文件,所以会运行缓慢。
  6. element若依 菜单点击改变背景色
  7. 诺基亚A7Android,诺基亚四款新机齐曝光 你最期待哪一款?
  8. SAP重置公司代码资产会计(FI-AA)数据-OABL
  9. 基于java+SpringBoot+HTML+Mysql音乐网站
  10. x509证书OpenSSL 数据结构