错误处理机制

SpringBoot默认的错误处理机制

  1. 原理

    1. 给容器中添加了一下组件

      1. DefaultErrorAttributes:帮我们分享页面信息
       @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;
      }
      
      1. BasicErrorController:处理默认/error请求
      @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);
              }
          }
      
      1. ErrorPageCustomizer:系统出现错误以后,来到error请求进行处理
      @Value("${error.path:/error}")
      private String path = "/error";
      
      1. DefaultErrorViewResolver

      2. 浏览器发送数据的请求头 在这里插入图片描述

      3. 其他客户端:accept:"/"

    2. 步骤

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

      2. 被BasicErrorController处理

        1. 响应页面,去哪个页面是由DefaultErrorViewResolver解析得到的

          protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
              Iterator var5 = this.errorViewResolvers.iterator();
          
              ModelAndView modelAndView;
              do {
                  if (!var5.hasNext()) {
                      return null;
                  }
          
                  ErrorViewResolver resolver = (ErrorViewResolver)var5.next();
                  //所有的ErrorViewResolver得到ModelAndView
                  modelAndView = resolver.resolveErrorView(request, status, model);
              } while(modelAndView == null);
          
              return modelAndView;
          }
          
        2. DefaultErrorViewResolver如何解析

          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/状态码
              String errorViewName = "error/" + viewName;
              //模板引擎可以解析这个页面地址就用模板引擎解析
              TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
              //模板引擎可用的情况下,返回errorViewName指定的视图地址
              //模板引擎不可用的话,就在静态资源文件夹下找errorViewName对应的页面。error/状态码.html
              return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
          }
          
          private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
              String[] var3 = this.resourceProperties.getStaticLocations();
              int var4 = var3.length;
          
              for(int var5 = 0; var5 < var4; ++var5) {
                  String location = var3[var5];
          
                  try {
                      Resource resource = this.applicationContext.getResource(location);
                      resource = resource.createRelative(viewName + ".html");
                      if (resource.exists()) {
                          return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model);
                      }
                  } catch (Exception var8) {
                  }
              }
          
              return null;
          }
          
  2. 定制错误响应

    1. 有模板引擎的情况下,error/状态码;【将错误页面命名为 错误状态码.html放在模板引擎文件夹下的error文件下】,发生此状态码的错误就会来到这个页面
      1. 我们可以使用4xx或者5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html)
      2. 页面能获取的信息
        1. timestamp:时间戳’
        2. status:状态码
        3. error:错误提示
        4. exception:异常对象
        5. message:异常消息
        6. errors:JSP303数据校验的错误信息都在这里
    2. 没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找
    3. 以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面
    @Bean(
         name = {"error"}
     )
     @ConditionalOnMissingBean(
         name = {"error"}
     )
     public View defaultErrorView() {
         return this.defaultErrorView;
     }
    
    public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
         if (response.isCommitted()) {
             String message = this.getMessage(model);
             logger.error(message);
         } else {
             response.setContentType(TEXT_HTML_UTF8.toString());
             StringBuilder builder = new StringBuilder();
             Object timestamp = model.get("timestamp");
             Object message = model.get("message");
             Object trace = model.get("trace");
             if (response.getContentType() == null) {
                 response.setContentType(this.getContentType());
             }
    
             builder.append("<html><body><h1>Whitelabel Error Page</h1>").append("<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>").append("<div id='created'>").append(timestamp).append("</div>").append("<div>There was an unexpected error (type=").append(this.htmlEscape(model.get("error"))).append(", status=").append(this.htmlEscape(model.get("status"))).append(").</div>");
             if (message != null) {
                 builder.append("<div>").append(this.htmlEscape(message)).append("</div>");
             }
    
             if (trace != null) {
                 builder.append("<div style='white-space:pre-wrap;'>").append(this.htmlEscape(trace)).append("</div>");
             }
    
             builder.append("</body></html>");
             response.getWriter().append(builder.toString());
         }
     }
    

    自定义错误提示

     <h1>status:[[${status}]]</h1>
     <h2>timestamp:[[${timestamp}]]</h2>
     <h2>error:[[${error}]]</h2>
     <h2>trace:[[${trace}]]</h2>
    
  3. 定制错误的json数据

    1. 自定义异常处理,返回自定义json数据
    //自定义异常
    public class UserNotExistException extends RuntimeException {
        public UserNotExistException() {
            super("用户不存在");
        }
    }
    
    //抛出异常
    @ResponseBody
    @RequestMapping("/hello")
    public String hello(@RequestParam("user") String user){
        if (user.equals("aaa")){
            throw new UserNotExistException();
        }
        return "Hello Web";
    }
    
    //异常处理控制器,返回json数据
    @ControllerAdvice
    public class MyExceptionHandler {
        @ResponseBody
        @ExceptionHandler(UserNotExistException.class)
        public Map<String, Object> handleException(Exception e){
            Map<String, Object> map = new HashMap<>();
            map.put("code","user,not exist");
            map.put("message",e.getMessage());
            return map;
        }
    }
    
    • 没有自适应效果,浏览器和客户端返回的都是json
    1. 转发到/error,进行自适应响应效果处理
    //异常处理器
    @ExceptionHandler(UserNotExistException.class)
    public String handleException(Exception e, HttpServletRequest request) {
        Map<String, Object> map = new HashMap<>();
        /**
         * 需要传入自己的错误状态码
         * Integer statusCode = (Integer)request.getAttribute("javax.servlet.error.status_code");
         */
        request.setAttribute("javax.servlet.error.status_code",500);
        map.put("code", "user,not exist");
        map.put("message", "用户数据错误");
        //扩展异常属性
        request.setAttribute("ext",map);
        return "forward:/error";
    }
    
    1. 将我们的定制数据携带出去
      出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法)

      1. 完全来编写一个ErrorController的实现类,或者编写AbstractErrorController的子类,放在容器中
      2. 页面上能用的数据,或者是json返回的数据都是通过errorAttributes.getErrorAttributes得到
      protected Map<String, Object> getErrorAttributes(HttpServletRequest request, ErrorAttributeOptions options) {
          WebRequest webRequest = new ServletWebRequest(request);
          return this.errorAttributes.getErrorAttributes(webRequest, options);
      }
      

      容器中DefaultErrorAttributes();默认进行数据处理的

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

      自定义ErrorAttributes

      //给容器中加入我们自己定义的ErrorAttributes
      @Component
      public class MyErrorAttributes extends DefaultErrorAttributes {
          //返回值的map就是页面喝json能获取的所有字段
          @Override
          public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
              Map<String, Object> map = super.getErrorAttributes(webRequest, options);
              map.put("company","whut");
              //获取异常处理器携带的数据
              Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);//request域中获取
              map.put("ext",ext);
              return map;
          }
      }
      
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一大岐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值