springboot的版本
2.0.2.RELEASE
springboot默认的错误处理机制
默认出错后的效果
- 使用浏览器请求,出错后返回的信息
- 使用postman请求,出错后返回的信息
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; } }
-
错误页面
<!-- 提供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