一、默认效果

Spring Boot在默认情况下,对浏览器访问和其他客户端访问的处理机制是不同的。

1、浏览器访问
浏览器访问返回错误页面,错误页面中带有错误状态码,错误类型,提示信息等。
错误处理原理 - 图1
浏览器发送的请求头:
错误处理原理 - 图2

2、其他客户端访问
其他客户端访问,默认响应一个JSON数据,JSON数据中带有错误状态码,错误类型,提示信息等。
错误处理原理 - 图3
其他客户端发送的请求头
错误处理原理 - 图4

二、原理说明

Spring Boot的错误处理的自动配置参照ErrorMvcAutoConfiguration,该自动配置类主要向容器中添加了以下四个组件。
一、DefaultErrorAttributes

  1. /**
  2. * 帮我们在页面共享信息,设置页面上的错误信息
  3. **/
  4. @Override
  5. public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,
  6. boolean includeStackTrace) {
  7. Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>();
  8. errorAttributes.put("timestamp", new Date());
  9. addStatus(errorAttributes, requestAttributes);
  10. addErrorDetails(errorAttributes, requestAttributes, includeStackTrace);
  11. addPath(errorAttributes, requestAttributes);
  12. return errorAttributes;
  13. }

二、BasicErrorController

  1. /**
  2. * 处理默认的/error请求
  3. **/
  4. @Controller
  5. @RequestMapping("${server.error.path:${error.path:/error}}")
  6. public class BasicErrorController extends AbstractErrorController {
  7. // 处理浏览发送的请求,产生html类型的数据
  8. @RequestMapping(produces = "text/html")
  9. public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
  10. HttpStatus status = getStatus(request);
  11. Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
  12. request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
  13. response.setStatus(status.value());
  14. // 视图解析器,决定要去哪个错误页面,包含页面地址和页面内容
  15. ModelAndView modelAndView = resolveErrorView(request, response, status, model);
  16. return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
  17. }
  18. // 处理其他客户端发送的请求,产生json数据
  19. @RequestMapping
  20. @ResponseBody
  21. public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
  22. Map<String, Object> body = getErrorAttributes(request,
  23. isIncludeStackTrace(request, MediaType.ALL));
  24. HttpStatus status = getStatus(request);
  25. return new ResponseEntity<Map<String, Object>>(body, status);
  26. }

三、ErrorPageCustomizer

  1. // 系统出现错误以后来到error请求进行处理(web.xml注册的错误页面规则)
  2. @Value("${error.path:/error}")
  3. private String path = "/error";

四、DefaultErrorViewResolver

  1. @Override
  2. public ModelAndView resolveErrorView(HttpServletRequest request,
  3. HttpStatus status, Map<String, Object> model) {
  4. ModelAndView modelAndView = resolve(String.valueOf(status), model);
  5. if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
  6. modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
  7. }
  8. return modelAndView;
  9. }
  10. private ModelAndView resolve(String viewName, Map<String, Object> model) {
  11. //去error目录下找和状态码一致的错误页面 例如:404.ftl
  12. String errorViewName = "error/" + viewName;
  13. // 模板引擎可以解析这个页面地址就用模板引擎解析
  14. TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
  15. .getProvider(errorViewName, this.applicationContext);
  16. if (provider != null) {
  17. // 模板引擎可用的情况下返回到errorViewName指定的视图地址
  18. return new ModelAndView(errorViewName, model);
  19. }
  20. // 模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面
  21. return resolveResource(errorViewName, model);
  22. }

一旦系统出现错误(例如:4xx/5xx),ErrorPageCustomizer就会生效,处理错误信息,然后来到/error请求,被BasicErrorController处理。

  1. /**
  2. * 具体响应页面由DefaultErrorViewResolver解析得到
  3. **/
  4. protected ModelAndView resolveErrorView(HttpServletRequest request,
  5. HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
  6. // 所有的ErrorViewResolver得到ModelAndView
  7. for (ErrorViewResolver resolver : this.errorViewResolvers) {
  8. ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
  9. if (modelAndView != null) {
  10. return modelAndView;
  11. }
  12. }
  13. return null;
  14. }

Spring Boot错误页面默认放在资源路径的/error目录下,页面名称即错误代码(例如:404.ftl),你可以用“x”做错误页面模糊匹配(例如:4xx.ftl 匹配所有4开头的错误码)。同时存在时,精确优先。