我爱编程

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

2018-06-11  本文已影响0人  缓慢移动的蜗牛

springboot的版本

2.0.2.RELEASE

springboot默认的错误处理机制

默认出错后的效果

默认错误页面.png postman请求的出错提示.png

springboot错误处理的原理

@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;
        }

    }
    
}
@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;
}

自定义错误响应

<!-- 提供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
上一篇下一篇

猜你喜欢

热点阅读