美文网首页我爱编程
springboot的错误处理机制以及自定义错误处理

springboot的错误处理机制以及自定义错误处理

作者: 缓慢移动的蜗牛 | 来源:发表于2018-06-11 18:05 被阅读0次

springboot的版本

2.0.2.RELEASE

springboot默认的错误处理机制

默认出错后的效果

  • 使用浏览器请求,出错后返回的信息
默认错误页面.png
  • 使用postman请求,出错后返回的信息
postman请求的出错提示.png

springboot错误处理的原理

  • ErrorMvcAutoConfiguration类中,注入了BasicErrorController来处理默认/error的请求
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
// Load before the main WebMvcAutoConfiguration so that the error View is available
@AutoConfigureBefore(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class })
public class ErrorMvcAutoConfiguration {
    
    //错误属性的定义
    @Bean
    @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
    public DefaultErrorAttributes errorAttributes() {
        return new DefaultErrorAttributes(
                this.serverProperties.getError().isIncludeException());
    }
    
    //处理错误信息的控制逻辑
    @Bean
    @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
    public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
        return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
                this.errorViewResolvers);
    }
    
    //默认的错误页面
    @Configuration
    @ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
    @Conditional(ErrorTemplateMissingCondition.class)
    protected static class WhitelabelErrorViewConfiguration {

        private final SpelView defaultErrorView = new SpelView(
                "<html><body><h1>Whitelabel Error Page</h1>"
                        + "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>"
                        + "<div id='created'>${timestamp}</div>"
                        + "<div>There was an unexpected error (type=${error}, status=${status}).</div>"
                        + "<div>${message}</div></body></html>");

        @Bean(name = "error")
        @ConditionalOnMissingBean(name = "error")
        public View defaultErrorView() {
            return this.defaultErrorView;
        }

        // If the user adds @EnableWebMvc then the bean name view resolver from
        // WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment.
        @Bean
        @ConditionalOnMissingBean
        public BeanNameViewResolver beanNameViewResolver() {
            BeanNameViewResolver resolver = new BeanNameViewResolver();
            resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
            return resolver;
        }

    }
    
}
  • BasicErrorController里面处理错误请求的方法
@Controller
//查找配置文件中的server.error.path,如果没有,再从配置文件中查找error.path,如果没有就采用默认的/error
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
   
    //定义了处理错误信息的默认路径  /error
   private final ErrorProperties errorProperties;
  
    //产生html类型的数据;浏览器发送的请求来到这个方法处理
    @RequestMapping(produces = "text/html")
    public ModelAndView errorHtml(HttpServletRequest request,
            HttpServletResponse response) {
        //获取请求的状态信息
        HttpStatus status = getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
                request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        //去寻找具体的页面
        ModelAndView modelAndView = resolveErrorView(request, response, status, model);
        return (modelAndView != null ? modelAndView : new ModelAndView("error", model));
    }
    
    
    //产生json数据,其他客户端来到这个方法处理;
    @RequestMapping
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);
        return new ResponseEntity<>(body, status);
    }
}
  • 寻找错误页面的处理流程
@RequestMapping(produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request,
      HttpServletResponse response) {
   HttpStatus status = getStatus(request);
   Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
         request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
   response.setStatus(status.value());
   //调用解析方法
   ModelAndView modelAndView = resolveErrorView(request, response, status, model);
   //如果都没有找到,就会采用默认的页面,此页面在ErrorMvcAutoConfiguration也已经注册,WhitelabelErrorViewConfiguration
   return (modelAndView != null ? modelAndView : new ModelAndView("error", model));
}

//调用此方法
protected ModelAndView resolveErrorView(HttpServletRequest request,
            HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
    for (ErrorViewResolver resolver : this.errorViewResolvers) {
        //用视图解析去,是寻找相关页面,没有配置的话,
        //使用org.springframework.boot.autoconfigure.web.servlet.error.DefaultErrorViewResolver
        ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
        if (modelAndView != null) {
            return modelAndView;
        }
    }
    return null;
}



@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
                                     Map<String, Object> model) {
    //默认去 error/  下面去寻找对应http状态码的页面,例如:404.html  500.html
    ModelAndView modelAndView = resolve(String.valueOf(status), model);
    
    //去寻找 4xx   5xx开头的页面
    if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
        modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
    }
    return modelAndView;
}

//去error/  路径下寻找页面
private ModelAndView resolve(String viewName, Map<String, Object> model) {
    String errorViewName = "error/" + viewName;
    TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
        .getProvider(errorViewName, this.applicationContext);
    if (provider != null) {
        return new ModelAndView(errorViewName, model);
    }
    return resolveResource(errorViewName, model);
}

//再去classpath:/META-INF/resources/", "classpath:/resources/","classpath:/static/", "classpath:/public/"这些路径下,去寻找对应的状态码的html页面
private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
    for (String location : this.resourceProperties.getStaticLocations()) {
        try {
            Resource resource = this.applicationContext.getResource(location);
            resource = resource.createRelative(viewName + ".html");
            if (resource.exists()) {
                return new ModelAndView(new HtmlResourceView(resource), model);
            }
        }
        catch (Exception ex) {
        }
    }
    return null;
}
  • 错误的json数据里面属性的来源

    这里面定义的属性有:

    timestamp:时间戳

    status:状态码

    error:错误提示

    exception:异常对象

    path:路径

    package org.springframework.boot.web.servlet.error;
    /**
     * Default implementation of {@link ErrorAttributes}. Provides the following attributes
     * when possible:
     * <ul>
     * <li>timestamp - The time that the errors were extracted</li>
     * <li>status - The status code</li>
     * <li>error - The error reason</li>
     * <li>exception - The class name of the root exception (if configured)</li>
     * <li>message - The exception message</li>
     * <li>errors - Any {@link ObjectError}s from a {@link BindingResult} exception
     * <li>trace - The exception stack trace</li>
     * <li>path - The URL path when the exception was raised</li>
     * </ul>
     *
     * @author Phillip Webb
     * @author Dave Syer
     * @author Stephane Nicoll
     * @author Vedran Pavic
     * @since 2.0.0
     * @see ErrorAttributes
     */
     
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public class DefaultErrorAttributes
          implements ErrorAttributes, HandlerExceptionResolver, Ordered {
    

自定义错误响应

  • 可以定义一个我们自己的错误类

    package com.nanc.exception;
    
    public class UserNotExistException extends RuntimeException{
    
       public UserNotExistException(){
          super("异常信息---用户不存在");
       }
    }
    
  • 自定义异常处理类

    package com.nanc.controller;
    
    import com.nanc.exception.UserNotExistException;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.HashMap;
    import java.util.Map;
    
    @ControllerAdvice
    public class MyExceptionHandler {
    
       //这个没有自适应效果(浏览器请求返回页面,其他的就返回json数据),可以转发到/error就可以自适应了
       //@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;
       //}
    
       //@ExceptionHandler(UserNotExistException.class)
       //public String handleException(Exception e) {
       // Map<String, Object> map = new HashMap<>();
       //  //自己定义的数据在返回的数据中无法使用
       // map.put("code", "user not exist");
       // map.put("message", e.getMessage());
       //
       // //转发到/error
       // return "forward:/error";
       //}
    
       @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 not exist");
          map.put("message", e.getMessage());
          request.setAttribute("ext", map);
    
          //转发到/error
          return "forward:/error";
       }
    
    
    }
    
  • 定制自己的一些错误属性

    package com.nanc.component;
    
    import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.WebRequest;
    
    import java.util.Map;
    
    //给容器中加入我们自己定义的ErrorAttributes
    @Component
    public class MyErrorAttributes extends DefaultErrorAttributes {
    
       @Override
       public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
          Map<String, Object> map =  super.getErrorAttributes(webRequest, includeStackTrace);
    
          map.put("company","xxxxx");
    
          //我们的异常处理器携带的数据,这些数据是从MyExceptionHandler中传递过来的
          Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);
          map.put("ext",ext);
          return map;
       }
    }
    
  • 错误页面

    错误页面.png
<!-- 提供5xx页面中的部分信息 -->
<!-- 使用默认的thymeleaf模板 -->
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
   <h1>status:[[${status}]]</h1>
   <h2>timestamp:[[${timestamp}]]</h2>
   <h2>timestamp:[[${#dates.format(timestamp, 'yyyy-MM-dd HH:mm:ss')}]]</h2>
   <h2>exception:[[${exception}]]</h2>
   <h2>message:[[${message}]]</h2>
   <h2>path:[[${path}]]</h2>
   <h2>errors:[[${errors}]]</h2>
   <h2>ext:[[${ext.code}]]</h2>
   <h2>ext:[[${ext.message}]]</h2>
</main>

注意事项

如果请求返回的是json数据,时间可能会差8个小时,解决办法,在application.yml添加如下配置

spring:
  mvc:
    date-format: yyyy-MM-dd
  jackson: # 解决差8个小时的问题
    time-zone: GMT+8
    date-format: yyyy-MM-dd HH:mm:ss

相关文章

网友评论

    本文标题:springboot的错误处理机制以及自定义错误处理

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