美文网首页
优雅、统一地处理异常

优雅、统一地处理异常

作者: 小尾巴1024 | 来源:发表于2019-01-24 15:24 被阅读0次

序言:

程序中不可避免会出现异常,有些异常是人为的,有些是系统造成的,有些是外部因素造成的(比如网络堵塞等)。但是我们有责任和义务去处理这些异常,给客户提供良好的交互!

现在大多数系统都采用了分层模式,比如MVC。但是往往有人把service跟controller紧紧的耦合在一起,复杂的逻辑几乎只能使用service中存储的全局对象来传递处理结果,包括异常。这样一来首先有违MVC模式,二来逻辑十分不清晰,难以维护。本文将遵循MVC的分层模式,来提供一些优雅、统一处理异常的建议,写得不好的地方,请慷慨指正!

错误的异常定义例子:

看过一些项目的异常是这样定义的:比如定义一个全局的AppException,然后所有地方都只抛出这个异常,并且把捕获的异常case到这个AppException中.会有如下问题:
1.只有一种异常类,无法精准区分开异常类型
2.打印出一些没用的日志信息,浪费日志存储空间
3.而且日志栈顶并不是最接近发生异常的代码位置
4.后期如果修改了异常类难以维护

自定义业务异常

public class ServiceException extends RuntimeException {
    //接收reason参数用来描述业务失败原因.
  public ServiceException(String reason) {  super(reason); }
}

比如有Controller业务如下,用户提交数据的时候需要验证 有效性合法性

  • 有效性: 比如用户所在地址,是否是合法地址,如果不存在,无效.
  • 合法性: 比如用户名只允许输入最多20个字符,用户提交了21个字符,不合法.
    有效性检查,可以交给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入口和响应的处理。

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

public void updateUser(User user) {
    User userOrig = userDao.getUserById(user.getUserID());
    if (null == userOrig) {
      throw new ServiceException("用户不存在");
    }
    if (userOrig.isLocked()) {
      throw new ServiceException("用户被锁定,不允许修改");
    }
    if (!user.getVersion().equals(userOrig.getVersion())) {
      throw new ServiceException("用户已经被别人修改过,请刷新重试");
    }
    // TODO dosomething  ... 
  }

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

接下来有2种选择:
在controller 使用try-catch进行处理.
直接把异常抛给上层框架统一处理.
第1种方式是不可取的 ,注意我们抛出的ServiceException,它仅仅逻辑处理异常,并且我们的方法前面没有声明throws ServiceException,这表示他是一个非受查异常.controller也没有关心会发生什么异常.

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

重点:解决方案

定义一个继承继承自RuntimeException的异常,并且包含一个接受一个错误原因的构造器,这样controller层也不需要知道异常,只要全局捕获到ServiceException做统一的处理即可。如此一来,我们只需要全局统一处理下 ServiceException 就可以了,很好,spring为我们提供了ControllerAdvice机制。
代码例子如下:

@ControllerAdvice(basePackages = { "com.xxx.xxx.bussiness.xxx" })
public class ModuleControllerAdvice {
  private static final Logger LOGGER = LoggerFactory.getLogger(ModuleControllerAdvice.class);
  private static final Logger SERVICE_LOGGER = LoggerFactory.getLogger(ServiceException.class);
  /**
   * 业务受理失败
   */
  @ResponseBody
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  @ExceptionHandler(ServiceException.class)
  private JSONResult handleServiceException(ServiceException exception) {
    String message = "业务受理失败,原因:" + exception.getLocalizedMessage();
    SERVICE_LOGGER.info(message);
    JSONResult json = new JSONResult();
    json.serCode(101); 
    json.setMessage(message); 
    return json;
  }
}

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

参考:https://mp.weixin.qq.com/s/GXSZ-3w3Px43fA3XEuyWxw

相关文章

  • 优雅、统一地处理异常

    序言: 程序中不可避免会出现异常,有些异常是人为的,有些是系统造成的,有些是外部因素造成的(比如网络堵塞等)。但是...

  • SpringBoot接口 - 如何优雅的写Controller并

    内容目录 为什么要优雅的处理异常 实现案例@ControllerAdvice异常统一处理Controller接口运...

  • springboot 构建统一异常&统一返回

    如何优雅的处理异常,并将异常优雅的封装到返回中进行统一返回?如果让我们专注于业务代码的书写呢,这篇就够了,加油。一...

  • spring/springmvc 全局异常处理

    1.在项目中为什么要统一异常处理 当异常返回到前端页面的时候可以统一处理,避免前端无法处理异常 不做统一异常处理,...

  • 统一异常处理

    一、什么是统一异常处理 1、制造异常 2、什么是统一异常处理我们想让异常结果也显示为统一的返回结果对象,并且统一处...

  • springboot全局异常处理

    一、单个controller范围的异常处理/** 统一异常处理 @return*/@RequestMapping(...

  • springboot之统一异常处理

    spring统一异常处理 使用spring的统一异常处理,我们就不再需要在业务代码中就行显式的捕获异常处理,在da...

  • 项目实践:后端接口统一规范的同时,如何优雅得扩展规范?

    前言 之前写过如何通过参数校验 + 统一相应码 + 统一异常处理来构建一个优雅后端接口体系: 我们做到了: 通过V...

  • 统一异常处理

    1.定义返回结果对象 2.定义返回结果工具类, 3.枚举消息状态 .4.自定义异常类 5.统一异常拦截 6.业务中...

  • 统一异常处理

    首先,利用枚举,来定义异常类型。定义枚举ResultEnum: 自定义异常,新建CustomException类:...

网友评论

      本文标题:优雅、统一地处理异常

      本文链接:https://www.haomeiwen.com/subject/vvlsjqtx.html