SpringBoot实战 之 异常处理篇

摘要:
在上面的示例中,调用接口时发生异常,但客户端收到了相对正常的响应。这是因为SpringBoot默认情况下提供了/error的映射,该映射在servlet容器中注册为全局错误页,以正确处理所有异常。例如,SpringBoot的数据交互部分使用了带注释的参数验证,但失败的原因没有以有效的方式报告给前端应用程序。

在互联网时代,我们所开发的应用大多是直面用户的,程序中的任何一点小疏忽都可能导致用户的流失,而程序出现异常往往又是不可避免的,那该如何减少程序异常对用户体验的影响呢?其实方法很简单,对异常进行捕获,然后给予相应的处理即可。但实现的方式却有好多种,例如:

try {
    ...
} catch (Exception e) {
    doSomeThing();
}

像这种标准的 try-catch 是可以解决问题,但如果让你在每个接口实现里面都 try-catch 一下,我想你应该是不太愿意的。那么下面来介绍下 SpringBoot 为我们提供的处理方式。

1. ErrorController 应用

首先,我们来模拟一下,出现异常的场景,方式比较简单,直接在正常的代码里面抛出一个异常即可。

这里写图片描述

在上面的示例中,调用接口时,出现了异常,但客户端却收到一个相对正常的响应,这是因为 SpringBoot 默认提供了一个 /error 的映射,该映射被注册为 Servlet 容器中的一个全局错误页面用来合理处理所有的异常情况。但示例中的响应报文不符合我们定义的数据规范,想要使其满足自己的数据规范,可以自己定义一个新的 ErrorController,代码如下:

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class FundaErrorController implements ErrorController {

    @Override
    public String getErrorPath() {
        return "/error";
    }

    @RequestMapping
    @ResponseBody
    public Result doHandleError() {
        return new Result(ResultCode.WEAK_NET_WORK);
    }
}

当我们再次访问该接口的时候会返回:

{
  "code": -1,
  "msg": "网络异常,请稍后重试",
  "data": null
}

2. ExceptionHandler 应用

熟悉 SpringMVC 的人应该都知道 @ExceptionHandler 这个注解,在 SpringBoot 里面,我们同样可以使用它来做异常捕获。

2.1. 单一 Controller 异常处理

这种方式使用场景较少,但作为学习 @ExceptionHandler 入门示例还是非常不错的,直接在对应的 Controller 里面增加一个异常处理的方法,并使用 @ExceptionHandler 标识它即可。

@ExceptionHandler(Exception.class)
public Result handleException() {
    return new Result(ResultCode.WEAK_NET_WORK);
}

这里写图片描述

客户端得到的效果与使用 ErrorController 完全一致,但对于服务端来说却不太一样,如果仔细观察这两种方式的日志输出的话,会发现使用 ErrorController 时,后台会打印出异常堆栈信息,而使用 @ExceptionHandler 却不会,这是因为这两种处理方式的流程存在着本质的差别。

  1. ErrorController: 调用 UserController 抛出异常时,自身没有做任何处理,所以会打印出堆栈信息,但这个异常会被 Servlet 容器捕捉到,Servlet 容器再将请求转发给注册好的异常处理映射 /error 做处理,客户端收到的实际是 ErrorController 的处理结果,而不是 UserController 的。

  2. ExceptionHandler: 异常的处理方法直接被定义在 UserController 里面,也就是说,在异常抛出的时候,UserController 会使用自己的方法去做异常处理,而不会抛出给 Servlet 容器,所以这个地方没有打印堆栈信息。

如果想要在后台添加堆栈信息的输出也非常简单,只需要将该异常作为一个参数传递给异常处理方法,然后在处理方法里面做相应的操作即可。

@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
    e.printStackTrace();
    return new Result(ResultCode.WEAK_NET_WORK);
}

2.2. 父级 Controller 异常处理

项目的往往存在着多个 Controller,而它们在异常处理方面有存在着很多的共性,这样就不太适合在每一个 Controller 里面都编写一个对应的异常处理方法。可以将异常处理方法向上挪移到父类中,然后所有的 Controller 统一继承父类即可。

定义父类 BaseController:

public class BaseController {

    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e) {
        e.printStackTrace();
        return new Result(ResultCode.WEAK_NET_WORK);
    }

}

UserController 通过继承 BaseController 完成异常处理:

@RestController
@RequestMapping("/sys/user")
public class UserController extends BaseController {
    ...
}

2.3. Advice 异常处理

对于使用父级 Controller 完成异常处理也有着它自己的缺点,那就是代码耦合严重,一旦哪天忘记继承 BaseController,异常又会直达客户了。想要解除这种耦合关系,可以使用 @ControllerAdvice 来协助处理。

@ControllerAdvice
@ResponseBody
public class ExceptionHandlerAdvice {

    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e) {
        e.printStackTrace();
        return new Result(ResultCode.WEAK_NET_WORK);
    }

}
3. 多类别异常处理

实际的开发场景中,异常是区分很多类别的,不同类别的异常需要给用户不同的反馈。例如,在 SpringBoot实战 之 数据交互篇 中有使用到注解式参数校验,但校验不通过原因并没有以有效的方式告之给前端应用。下面我们通过上面提到的异常处理方式来完成这个功能:

首先,在 ResultCode 类中定义好 参数错误 的 code,代码如下:

PARAMETER_ERROR(10101, "参数错误")
  • 1

在 ExceptionHandlerAdvice 中添加对应的异常处理方法:

@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleIllegalParamException(MethodArgumentNotValidException e) {
    List<ObjectError> errors = e.getBindingResult().getAllErrors();
    String tips = "参数不合法";
    if (errors.size() > 0) {
        tips = errors.get(0).getDefaultMessage();
    }
    Result result = new Result(ResultCode.PARAMETER_ERROR);
    result.setMsg(tips);
    return result;
}

当应用程序抛出 MethodArgumentNotValidException 时,会精确匹配到该方法,在方法里面会获取到校验结果,并将所有校验错误中的第一条返回给前端应用。

这里写图片描述

这样的话,就可以在 ExceptionHandlerAdvice 里面添加各种各样的异常处理方法,以适合不同的应用场景。

项目的 github 地址:https://github.com/qchery/funda

免责声明:文章转载自《SpringBoot实战 之 异常处理篇》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇大端模式和小端模式盲去模糊之模型与算法下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Java中的异常处理机制的简单原理和应用?

程序运行过程中可能出现各种“非预期”情况,这些非预期情况可能导致程序非正常结束。 为了提高程序的健壮性,Java提供了异常处理机制: try { s1... s2... s3... } catch(Exception ex) { //对异常情况的修复处理 } 对于上面处理流程,当程序执行try块里的s1、s2、s3遇到异常时,Java虚拟机将会把这个异常...

C++异常处理assert,throw,exit用法

常见的几个小细节问题。 assert应用:       在现实世界中,我们脑袋时刻都在判断对与错,对的事情我们会继续深入下去,而错的事情我们会马上停止,那么在编程开发中我们如何赋予程序这种判断事物对错的能力呢?其中一个方案就可以使用断言assert,我们最常用的地方就是在函数中检查形参的数据合法性。 1、函数所属头文件:         assert.h...

Android 异常捕获

  在用户使用APP时,如果APP毫无征兆的突然退出程序,又没有任何提示信息。我想这是一种最差劲的用户体验了吧,如果是我估计干脆就直接卸载APP了。因此,作为Android开发者对于这种情况的发生一定要有处理才行。否则,对于大多数最求完美的程序员而言自己也不能原谅自己。其实捕获全局异常,还可以做一个登出处理,比如用户在登陆APP后,服务端会通过seesio...

Java异常处理008:RestTemplate请求Could not extract response: no suitable HttpMessageConverter found for response type.... content type [text/html;charset=UTF-8]异常

Java异常处理008:RestTemplate请求Could not extract response: no suitable HttpMessageConverter found for response type....  content type [text/html;charset=UTF-8]异常 start   1-异常日志: 2020-1...

CSE(Corrupted State Exceptions) 严重异常处理办法

原因分析 出现这个问题说明.NET版本至少是4.0,因为微软在.NET 4.0版本中更改了异常处理机制。微软认为catch(Exception)这种写法是不负责任的,程序员应该按照异常严重类别决定程序是否继续执行。然而事实是catch(Exception)遍地开花,程序出现异常后继续顽强地执行,然后内存报错,系统报错,蓝屏,用户来一句"破系统"。现在微软不...

Resttemplateget带特殊字符调用 异常处理总结

Resttemplateget带特殊字符调用 异常处理总结 Resttemplate设置Accept RestTemplate restTemplate = new RestTemplate(); HttpHeaders httpHeaders = new HttpHeaders(); List<MediaType> acceptableMed...