SpringBoot学习-错误处理机制

SpringBoot默认的错误处理机制

默认效果:

  1. 浏览器,返回一个默认的错误页面

    浏览器默认

    • 浏览器发送请求的请求头:

      浏览器请求头

  2. 如果是其他客户端,默认响应一个json数据

    其他客户端默认

    • 请求头信息:

      其他客户端请求头

原理:

可以参照ErrorMvcAutoConfiguration;错误处理的自动配置;

给容器中添加了一下组件:

  • DefaultErrorAttributes

    • 帮我们在页面共享信息

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      /** @deprecated */
      @Deprecated
      public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
      Map<String, Object> errorAttributes = new LinkedHashMap();
      errorAttributes.put("timestamp", new Date());
      this.addStatus(errorAttributes, webRequest);
      this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
      this.addPath(errorAttributes, webRequest);
      return errorAttributes;
      }
  • BasicErrorController

    • 处理默认/error请求

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      @Controller
      @RequestMapping({"${server.error.path:${error.path:/error}}"})
      public class BasicErrorController extends AbstractErrorController {
      @RequestMapping(
      produces = {"text/html"}//产生html类型的数据,浏览器发送的请求来到这个方法处理
      )
      public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
      HttpStatus status = this.getStatus(request);
      Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
      response.setStatus(status.value());
      //去哪个页面作为错误页面,包含页面地址和页面内容
      ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
      return modelAndView != null ? modelAndView : new ModelAndView("error", model);
      }

      @RequestMapping//产生json数据,其他客户端发送的请求来到这个方法处理
      public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
      HttpStatus status = this.getStatus(request);
      if (status == HttpStatus.NO_CONTENT) {
      return new ResponseEntity(status);
      } else {
      Map<String, Object> body = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
      return new ResponseEntity(body, status);
      }
      }
  • ErrorPageCustomizer

    • 系统出现错误以后来到error请求进行处理;(web.xml注册的错误页面规则)

      1
      2
      @Value("${error.path:/error}")
      private String path = "/error";
  • DefaultErrorViewResolver

    • 默认SpringBoot可以去找到一个页面? error/404

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
      ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model);
      if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
      modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model);
      }

      return modelAndView;
      }

      private ModelAndView resolve(String viewName, Map<String, Object> model) {
      //默认SpringBoot可以去找到一个页面? error/404
      String errorViewName = "error/" + viewName;
      //模板引擎可以解析这个页面地址就用模板引擎解析
      TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
      //模板引擎可用的情况下返回到errorViewName指定的视图地址
      //模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面 error/404.html
      return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
      }

步骤:

​ 一旦系统出现4xx或者5xx之类的错误,ErrorPageCustomizer就会生效(定制错误的响应规则);就会来到/error请求;就会被BasicErrorController处理;

​ 1)响应页面:去哪个页面是由DefaultErrorViewResolver解析得到的

1
2
3
4
5
6
7
8
9
10
11
12
13
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
//所有的ErrorViewResolver得到modelAndView
Iterator var5 = this.errorViewResolvers.iterator();
ModelAndView modelAndView;
do {
if (!var5.hasNext()) {
return null;
}
ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
modelAndView = resolver.resolveErrorView(request, status, model);
} while(modelAndView == null);
return modelAndView;
}

如何定制错误响应

如何定制错误的页面

1、用模板引擎的情况下:error/状态码

  1. 精确匹配状态码(优先寻找)
    • 将错误页面命名为 错误状态码.html
    • 将html页面放在模板引擎文件夹里面的error文件夹下
    • 发生此状态码的错误就会来到对应的页面
  2. 匹配同类型的错误
    • 我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,
    • 精确优先(优先寻找精确的 状态码.html )
  3. 页面能获取的信息:
    • timestamp:时间戳
    • status:状态码
    • error:错误提示
    • exception:异常对象
    • message:异常消息
    • errors:JSR303数据校验的错误都在这里

2、没有模板引擎(模板引擎找不到这个错误页面):静态资源文件夹

3、以上都没有错误页面,就是默认来到SpringBoot默认错误提示页面

如何定制错误的json数据

开启信息显示

1
2
server.error.include-exception=true
server.error.include-message=always

1、自定义异常处理&返回定制json数据

  1. 自定义异常

    1
    2
    3
    4
    5
    public class UserNotExistException extends RuntimeException{
    public UserNotExistException() {
    super("用户不存在");
    }
    }
  2. 自定义异常处理器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @ControllerAdvice
    public class MyExceptionHandler {
    //浏览器、其他客户端返回的都是json
    @ResponseBody
    @ExceptionHandler(UserNotExistException.class)
    public Map<String,Object> handleException(Exception e){
    Map<String,Object> map = new HashMap<>();
    map.put("code","user.notexist");
    map.put("message",e.getMessage());
    return map;
    }
    }
  3. 这种模式没有自适应效果(浏览器返回页面,其他客户端返回json)

2、转发到/error进行自适应响应效果处理

  1. 修改自定义异常处理器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @ExceptionHandler(UserNotExistException.class)
    public String handleException(Exception e, HttpServletRequest request){
    Map<String,Object> map = new HashMap<>();
    //传入我们自己的错误状态码 4xx 5xx,否则就不会进入定制错误页面的解析流程
    /**
    * Integer statusCode = (Integer) request
    .getAttribute("javax.servlet.error.status_code");
    */
    request.setAttribute("javax.servlet.error.status_code",500);
    map.put("code","user.notexist");
    map.put("message",e.getMessage());
    //转发到/error
    return "forward:/error";

    }

将我们的定制数据携带出去

出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法)

  1. 方式一:完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;

  2. 方式二:页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到;

    • 默认使用容器中的DefaultErrorAttributes.getErrorAttributes(),进行数据处理;

    • 自定义ErrorAttributes

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      //给容器中加入我们自己定义的ErrorAttributes
      @Component
      public class MyErrorAttributes extends DefaultErrorAttributes {
      //返回的map就是页面和json能获取的所有字段
      @Override
      public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
      Map<String, Object> map= super.getErrorAttributes(webRequest, includeStackTrace);
      map.put("author", "ligangit");

      //我们的异常处理器携带的请求数据
      //0 表示从request域中获取数据
      //1 表示从session域中获取数据
      Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);
      map.put("ext", ext);
      return map;
      }
      }
    • 修改异常处理器

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      @ControllerAdvice
      public class MyExceptionHandler {
      //浏览器、其他客户端返回的都是json
      @ExceptionHandler(UserNotExistException.class)
      public String handleException(Exception e, HttpServletRequest request){
      Map<String,Object> map = new HashMap<>();
      //传入我们自己的错误状态码 4xx 5xx,否则就不会进入定制错误页面的解析流程
      /**
      * Integer statusCode = (Integer) request
      .getAttribute("javax.servlet.error.status_code");
      */
      request.setAttribute("javax.servlet.error.status_code",500);
      map.put("code","user.notexist");
      map.put("message",e.getMessage());
      //将我们自己定义的异常数据传递
      request.setAttribute("ext",map);
      //转发到/error
      return "forward:/error";

      }
      }
  3. 最终效果

    • 响应是自适应的,可以通过定制ErrorAttributes改变需要返回的内容

    • 浏览器

      浏览器异常结果

    • 其他客户端

      其他客户端异常结果

最后更新: 2020年07月30日 14:45

原始链接: http://ligangit.com/2020/07/30/SpringBoot学习-错误处理机制/

× 请我吃糖~
打赏二维码