SpringBoot中的全局异常处理

前端之家收集整理的这篇文章主要介绍了SpringBoot中的全局异常处理前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

本篇要点

  • 介绍SpringBoot默认的异常处理机制。
  • 如何定义错误页面
  • 如何自定义异常数据。
  • 如何自定义视图解析。
  • 介绍@ControllerAdvice注解处理异常。

一、SpringBoot默认的异常处理机制

默认情况下,SpringBoot为以下两种情况提供了不同的响应方式:

  1. Browser Clients浏览器客户端:通常情况下请求头中的Accept会包含text/html,如果未定义/error的请求处理,就会出现如下html页面:Whitelabel Error Page,关于error页面的定制,接下来会详细介绍。

  1. Machine Clients机器客户端:Ajax请求,返回ResponseEntity实体json字符串信息。
  1. {
  2. "timestamp": "2020-10-30T15:01:17.353+00:00","status": 500,"error": "Internal Server Error","trace": "java.lang.ArithmeticException: / by zero...","message": "/ by zero","path": "/"
  3. }

SpringBoot默认提供了程序出错的结果映射路径/error,这个请求的处理逻辑在BasicErrorController中处理,处理逻辑如下:

  1. // 判断mediaType类型是否为text/html
  2. @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
  3. public ModelAndView errorHtml(HttpServletRequest request,HttpServletResponse response) {
  4. HttpStatus status = getStatus(request);
  5. Map<String,Object> model = Collections
  6. .unmodifiableMap(getErrorAttributes(request,getErrorAttributeOptions(request,MediaType.TEXT_HTML)));
  7. response.setStatus(status.value());
  8. // 创建ModelAndView对象,返回页面
  9. ModelAndView modelAndView = resolveErrorView(request,response,status,model);
  10. return (modelAndView != null) ? modelAndView : new ModelAndView("error",model);
  11. }
  12. @RequestMapping
  13. public ResponseEntity<Map<String,Object>> error(HttpServletRequest request) {
  14. HttpStatus status = getStatus(request);
  15. if (status == HttpStatus.NO_CONTENT) {
  16. return new ResponseEntity<>(status);
  17. }
  18. Map<String,Object> body = getErrorAttributes(request,MediaType.ALL));
  19. return new ResponseEntity<>(body,status);
  20. }

二、错误页面的定制

相信Whitelabel Error Pag页面我们经常会遇到,这样体验不是很好,在SpringBoot中可以尝试定制错误页面,定制方式主要分静态和动态两种:

  1. 静态异常页面

classpath:/public/errorclasspath:/static/error路径下定义相关页面文件名应为确切的状态代码,如404.html,或系列掩码,如4xx.html。

举个例子,如果你想匹配404或5开头的所有状态代码到静态的html文件,你的文件目录应该是下面这个样子:

  1. src/
  2. +- main/
  3. +- java/
  4. | + <source code>
  5. +- resources/
  6. +- public/
  7. +- error/
  8. | +- 404.html
  9. | +- 5xx.html
  10. +- <other public assets>

如果500.html和5xx.html同时生效,那么优先展示500.html页面

  1. 使用模板的动态页面

放置在classpath:/templates/error路径下:这里使用thymeleaf模板举例:

  1. src/
  2. +- main/
  3. +- java/
  4. | + <source code>
  5. +- resources/
  6. +- templates/
  7. +- error/
  8. | +- 5xx.html
  9. +- <other templates>

页面如下:

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <Meta charset="UTF-8">
  5. <title>5xx</title>
  6. </head>
  7. <body>
  8. <h1>5xx</h1>
  9. <p th:text="'error -->'+ ${error}"></p>
  10. <p th:text="'status -->' + ${status}"></p>
  11. <p th:text="'timestamp -->' + ${timestamp}"></p>
  12. <p th:text="'message -->' + ${message}"></p>
  13. <p th:text="'path -->' +${path}"></p>
  14. <p th:text="'trace -->' + ${trace}"></p>
  15. </body>
  16. </html>

如果静态页面和动态页面同时存在且都能匹配,SpringBoot对于错误页面的优先展示规则如下:

如果发生了500错误

动态500 -> 静态500 -> 动态5xx -> 静态5xx

三、自定义异常数据

默认的数据主要是以下几个,这些数据定义在org.springframework.boot.web.servlet.error.DefaultErrorAttributes中,数据的定义在getErrorAttributes方法中。

DefaultErrorAttributes类在org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration自动配置类中定义:

  1. @Bean
  2. @ConditionalOnMissingBean(value = ErrorAttributes.class,search = SearchStrategy.CURRENT)
  3. public DefaultErrorAttributes errorAttributes() {
  4. return new DefaultErrorAttributes();
  5. }

如果我们没有提供ErrorAttributes的实例,SpringBoot默认提供一个DefaultErrorAttributes实例。

因此,我们就该知道如何去自定义异常数据属性

  1. 实现ErrorAttributes接口。
  2. 继承DefaultErrorAttributes,本身已经定义对异常数据的处理,继承更具效率。

定义方式如下:

  1. /**
  2. * 自定义异常数据
  3. * @author Summerday
  4. */
  5. @Slf4j
  6. @Component
  7. public class MyErrorAttributes extends DefaultErrorAttributes {
  8. @Override
  9. public Map<String,Object> getErrorAttributes(WebRequest webRequest,ErrorAttributeOptions options) {
  10. Map<String,Object> map = super.getErrorAttributes(webRequest,options);
  11. if(map.get("status").equals(500)){
  12. log.warn("服务器内部异常");
  13. }
  14. return map;
  15. }
  16. }

四、自定义异常视图

自定义视图的加载逻辑存在于BasicErrorController类的errorHtml方法中,用于返回一个ModelAndView对象,这个方法中,首先通过getErrorAttributes获取到异常数据,然后调用resolveErrorView去创建一个ModelAndView对象,只有创建失败的时候,用户才会看到默认的错误提示页面

  1. @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
  2. public ModelAndView errorHtml(HttpServletRequest request,Object> model = Collections
  3. .unmodifiableMap(
  4. //ErrorAttributes # getErrorAttributes
  5. getErrorAttributes(request,MediaType.TEXT_HTML)));
  6. response.setStatus(status.value());
  7. // E
  8. ModelAndView modelAndView = resolveErrorView(request,model);
  9. }

DefaultErrorViewResolver类的resolveErrorView方法

  1. @Override
  2. public ModelAndView resolveErrorView(HttpServletRequest request,HttpStatus status,Map<String,Object> model) {
  3. // 以异常状态码作为视图名去locations路径中去查找页面
  4. ModelAndView modelAndView = resolve(String.valueOf(status.value()),model);
  5. // 如果找不到,以4xx,5xx series去查找
  6. if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
  7. modelAndView = resolve(SERIES_VIEWS.get(status.series()),model);
  8. }
  9. return modelAndView;
  10. }

那么如何自定义呢?和自定义异常数据相同,如果我们定义了一个ErrorViewResolver的实例,默认的配置就会失效。

  1. /**
  2. * 自定义异常视图解析
  3. * @author Summerday
  4. */
  5. @Component
  6. public class MyErrorViewResolver extends DefaultErrorViewResolver {
  7. public MyErrorViewResolver(ApplicationContext applicationContext,ResourceProperties resourceProperties) {
  8. super(applicationContext,resourceProperties);
  9. }
  10. @Override
  11. public ModelAndView resolveErrorView(HttpServletRequest request,Object> model) {
  12. return new ModelAndView("/hyh/resolve",model);
  13. }
  14. }

此时,SpringBoot将会去/hyh目录下寻找resolve.html页面

五、@ControllerAdvice注解处理异常

前后端分离的年代,后端往往需要向前端返回统一格式的json信息,以下为封装的AjaxResult对象:

  1. public class AjaxResult extends HashMap<String,Object> {
  2. //状态码
  3. public static final String CODE_TAG = "code";
  4. //返回内容
  5. public static final String MSG_TAG = "msg";
  6. //数据对象
  7. public static final String DATA_TAG = "data";
  8. private static final long serialVersionUID = 1L;
  9. public AjaxResult() {
  10. }
  11. public AjaxResult(int code,String msg) {
  12. super.put(CODE_TAG,code);
  13. super.put(MSG_TAG,msg);
  14. }
  15. public AjaxResult(int code,String msg,Object data) {
  16. super.put(CODE_TAG,msg);
  17. if (data != null) {
  18. super.put(DATA_TAG,data);
  19. }
  20. }
  21. public static AjaxResult ok() {
  22. return AjaxResult.ok("操作成功");
  23. }
  24. public static AjaxResult ok(Object data) {
  25. return AjaxResult.ok("操作成功",data);
  26. }
  27. public static AjaxResult ok(String msg) {
  28. return AjaxResult.ok(msg,null);
  29. }
  30. public static AjaxResult ok(String msg,Object data) {
  31. return new AjaxResult(HttpStatus.OK.value(),msg,data);
  32. }
  33. public static AjaxResult error() {
  34. return AjaxResult.error("操作失败");
  35. }
  36. public static AjaxResult error(String msg) {
  37. return AjaxResult.error(msg,null);
  38. }
  39. public static AjaxResult error(String msg,Object data) {
  40. return new AjaxResult(HttpStatus.INTERNAL_SERVER_ERROR.value(),data);
  41. }
  42. public static AjaxResult error(int code,String msg) {
  43. return new AjaxResult(code,null);
  44. }
  45. }

根据业务的需求不同,我们往往也需要自定义异常类,便于维护:

  1. /**
  2. * 自定义异常
  3. *
  4. * @author Summerday
  5. */
  6. public class CustomException extends RuntimeException {
  7. private static final long serialVersionUID = 1L;
  8. private Integer code;
  9. private final String message;
  10. public CustomException(String message) {
  11. this.message = message;
  12. }
  13. public CustomException(String message,Integer code) {
  14. this.message = message;
  15. this.code = code;
  16. }
  17. public CustomException(String message,Throwable e) {
  18. super(message,e);
  19. this.message = message;
  20. }
  21. @Override
  22. public String getMessage() {
  23. return message;
  24. }
  25. public Integer getCode() {
  26. return code;
  27. }
  28. }

@ControllerAdvice和@RestControllerAdvice这俩注解的功能之一,就是做到Controller层面的异常处理,而两者的区别,与@Controller和@RestController差不多。

@ExceptionHandler指定需要处理的异常类,针对自定义异常,如果是ajax请求,返回json信息,如果是普通web请求,返回ModelAndView对象。

  1. /**
  2. * 全局异常处理器
  3. * @author Summerday
  4. */
  5. @RestControllerAdvice
  6. public class GlobalExceptionHandler {
  7. private static final Logger log =
  8. LoggerFactory.getLogger(GlobalExceptionHandler.class);
  9. @ExceptionHandler(CustomException.class)
  10. public Object handle(HttpServletRequest request,CustomException e) {
  11. AjaxResult info = AjaxResult.error(e.getMessage());
  12. log.error(e.getMessage());
  13. // 判断是否为ajax请求
  14. if (isAjaxRequest(request)) {
  15. return info;
  16. }
  17. ModelAndView mv = new ModelAndView();
  18. mv.setViewName("custom"); // templates/custom.html
  19. mv.addAllObjects(info);
  20. mv.addObject("url",request.getRequestURL());
  21. return mv;
  22. }
  23. private boolean isAjaxRequest(HttpServletRequest request) {
  24. return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
  25. }
  26. }

在Controller层,人为定义抛出异常:

  1. @RestController
  2. public class TestController {
  3. @GetMapping("/ajax")
  4. public AjaxResult ajax() {
  5. double alpha = 0.9;
  6. if (Math.random() < alpha) {
  7. throw new CustomException("自定义异常!");
  8. }
  9. return AjaxResult.ok();
  10. }
  11. }

最后,通过/templates/custom.html定义的动态模板页面展示数据:

  1. <!DOCTYPE html>
  2. <html lang="en" xmlns:th="http://www.thymeleaf.org">
  3. <head>
  4. <Meta charset="UTF-8">
  5. <title>自定义界面</title>
  6. </head>
  7. <body>
  8. <p th:text="'msg -->'+ ${msg}"></p>
  9. <p th:text="'code -->'+ ${code}"></p>
  10. <p th:text="'url -->'+ ${url}"></p>
  11. </body>
  12. </html>

源码下载

本文内容均为对优秀博客及官方文档总结而得,原文地址均已在文中参考阅读处标注。最后,文中的代码样例已经全部上传至Gitee:https://gitee.com/tqbx/springboot-samples-learn

参考阅读

猜你在找的Springboot相关文章