一、SpringBoot默认的错误处理机制
1、默认效果
(1)浏览器:返回一个默认的错误界面
浏览器发送请求的请求头:
(2)如果是其他客户端,默认响应一个 json 数据
2、原理
参照 ErrorMvcAutoConfiguration:错误处理的自动配置;
给容器中添加了以下组件:
(1)DefaultErrorAttributes:共享页面信息
帮我们在页面共享信息 ---BasicErrorController--- @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 ? new ModelAndView("error", model) : modelAndView); } ---DefaultErrorAttributes--- @Override public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) { Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>(); errorAttributes.put("timestamp", new Date()); addStatus(errorAttributes, requestAttributes); addErrorDetails(errorAttributes, requestAttributes, includeStackTrace); addPath(errorAttributes, requestAttributes); return errorAttributes; }
(2)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 = 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 ? new ModelAndView("error", model) : modelAndView); } @RequestMapping @ResponseBody //产生 json 类型数据 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<Map<String, Object>>(body, status); } }
(3)ErrorPageCustomizer:出现异常来到 /error 请求
---ErrorMvcAutoConfiguration--- private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered { private final ServerProperties properties; protected ErrorPageCustomizer(ServerProperties properties) { this.properties = properties; } @Override public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix() + this.properties.getError().getPath()); errorPageRegistry.addErrorPages(errorPage); } @Override public int getOrder() { return 0; } } ---ErrorProperties--- @Value("${error.path:/error}") private String path = "/error"; 系统出现错误以后来到 error 请求进行处理;(web.xml注册的错误页面规则)
(4)DefaultErrorViewResolver:异常视图解析器
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered { @Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { ModelAndView modelAndView = resolve(String.valueOf(status), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { modelAndView = resolve(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); if (provider != null) {
//模板引擎可用的情况下返回到 errorViewName 指定的视图地址 return new ModelAndView(errorViewName, model); }
//模板引擎不可用,就在静态资源文件夹下找 errorViewName 对应的页面 error/404.html return resolveResource(errorViewName, model); } 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; } }
步骤:
① 一但系统出现4xx或者5xx之类的错误,ErrorPageCustomizer 就会生效(定制错误的响应规则)就会来到 /error 请求;
② 来到 /error 请求,就会被 BasicErrorController 处理;
③ 响应页面,去哪个页面的由 DefaultErrorViewResolver 解析得到的;
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
//所有的 ErrorViewResolver 得到 ModelAndView for (ErrorViewResolver resolver : this.errorViewResolvers) { ModelAndView modelAndView = resolver.resolveErrorView(request, status, model); if (modelAndView != null) { return modelAndView; } } return null; }
二、如何定制错误响应
1、如何定义错误的页面
(1)有模板引擎的情况下;error/状态码;
①【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到对应的页面;
② 我们可以使用 4xx 和 5xx 作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html);
页面能获取的信息:
timestamp:时间戳 status:状态码 error:错误提示 exception:异常对象 message:异常消息 errors:JSR303数据校验的错误都在这里
(2)没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找;
(3)以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;
ErrorMvcAutoConfiguration 中配置了默认异常视图 error: