在日常的 Web 开发中,会经常遇到大大小小的异常,此时往往需要一个统一的异常处理机制,来保证客户端能接收较为友好的提示。Spring Boot 同样提供了一套默认的异常处理机制,本节将对它进行详细的介绍。
图1:Spring Boot 默认错误白页
{ "timestamp": "2021-07-12T07:05:29.885+00:00", "status": 404, "error": "Not Found", "message": "No message available", "path": "/m1ain.html" }
@Bean public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) { return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath); }
@Override public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { //将请求转发到 /errror(this.properties.getError().getPath())上 ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath())); // 注册错误页面 errorPageRegistry.addErrorPages(errorPage); }
@Bean @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT) public BasicErrorController basicErrorController(ErrorAttributes errorAttributes, ObjectProvider<ErrorViewResolver> errorViewResolvers) { return new BasicErrorController(errorAttributes, this.serverProperties.getError(), errorViewResolvers.orderedStream().collect(Collectors.toList())); }
//BasicErrorController 用于处理 “/error” 请求 @Controller @RequestMapping("${server.error.path:${error.path:/error}}") public class BasicErrorController extends AbstractErrorController { ...... /** * 该方法用于处理浏览器客户端的请求发生的异常 * 生成 html 页面来展示异常信息 * @param request * @param response * @return */ @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { //获取错误状态码 HttpStatus status = getStatus(request); //getErrorAttributes 根据错误信息来封装一些 model 数据,用于页面显示 Map<String, Object> model = Collections .unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML))); //为响应对象设置错误状态码 response.setStatus(status.value()); //调用 resolveErrorView() 方法,使用错误视图解析器生成 ModelAndView 对象(包含错误页面地址和页面内容) ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); } /** * 该方法用于处理机器客户端的请求发生的错误 * 产生 JSON 格式的数据展示错误信息 * @param request * @return */ @RequestMapping public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { HttpStatus status = getStatus(request); if (status == HttpStatus.NO_CONTENT) { return new ResponseEntity<>(status); } Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL)); return new ResponseEntity<>(body, status); } ...... }
返回值类型 | 方法声明 | 客户端类型 | 错误信息返类型 |
---|---|---|---|
ModelAndView | errorHtml(HttpServletRequest request, HttpServletResponse response) | 浏览器客户端 | text/html(错误页面) |
ResponseEntity<Map<String, Object>> | error(HttpServletRequest request) | 机器客户端(例如安卓、IOS、Postman 等等) | JSON |
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) { //获取容器中的所有的错误视图解析器来处理该异常信息 for (ErrorViewResolver resolver : this.errorViewResolvers) { //调用错误视图解析器的 resolveErrorView 解析到错误视图页面 ModelAndView modelAndView = resolver.resolveErrorView(request, status, model); if (modelAndView != null) { return modelAndView; } } return null; }
@Bean @ConditionalOnBean(DispatcherServlet.class) @ConditionalOnMissingBean(ErrorViewResolver.class) DefaultErrorViewResolver conventionErrorViewResolver() { return new DefaultErrorViewResolver(this.applicationContext, this.resources); }
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered { private static final Map<HttpStatus.Series, String> SERIES_VIEWS; static { Map<HttpStatus.Series, String> views = new EnumMap<>(HttpStatus.Series.class); views.put(Series.CLIENT_ERROR, "4xx"); views.put(Series.SERVER_ERROR, "5xx"); SERIES_VIEWS = Collections.unmodifiableMap(views); } ...... @Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { //尝试以错误状态码作为错误页面名进行解析 ModelAndView modelAndView = resolve(String.valueOf(status.value()), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { //尝试以 4xx 或 5xx 作为错误页面页面进行解析 modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map<String, Object> model) { //错误模板页面,例如 error/404、error/4xx、error/500、error/5xx String errorViewName = "error/" + viewName; //当模板引擎可以解析这些模板页面时,就用模板引擎解析 TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext); if (provider != null) { //在模板能够解析到模板页面的情况下,返回 errorViewName 指定的视图 return new ModelAndView(errorViewName, model); } //若模板引擎不能解析,则去静态资源文件夹下查找 errorViewName 对应的页面 return resolveResource(errorViewName, model); } private ModelAndView resolveResource(String viewName, Map<String, Object> model) { //遍历所有静态资源文件夹 for (String location : this.resources.getStaticLocations()) { try { Resource resource = this.applicationContext.getResource(location); //静态资源文件夹下的错误页面,例如error/404.html、error/4xx.html、error/500.html、error/5xx.html resource = resource.createRelative(viewName + ".html"); //若静态资源文件夹下存在以上错误页面,则直接返回 if (resource.exists()) { return new ModelAndView(new DefaultErrorViewResolver.HtmlResourceView(resource), model); } } catch (Exception ex) { } } return null; } ...... }
@Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes(); }
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered { ...... @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) { Map<String, Object> errorAttributes = getErrorAttributes(webRequest, options.isIncluded(Include.STACK_TRACE)); if (!options.isIncluded(Include.EXCEPTION)) { errorAttributes.remove("exception"); } if (!options.isIncluded(Include.STACK_TRACE)) { errorAttributes.remove("trace"); } if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") != null) { errorAttributes.remove("message"); } if (!options.isIncluded(Include.BINDING_ERRORS)) { errorAttributes.remove("errors"); } return errorAttributes; } private Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String, Object> errorAttributes = new LinkedHashMap<>(); errorAttributes.put("timestamp", new Date()); addStatus(errorAttributes, webRequest); addErrorDetails(errorAttributes, webRequest, includeStackTrace); addPath(errorAttributes, webRequest); return errorAttributes; } ...... }
本文链接:http://task.lmcjl.com/news/6005.html