spring mvc与错误处理

227 阅读8分钟

总结

sendError+抛出异常

  • sendError:服务器异常处理
    • org.apache.catalina.core.StandardHostValve#throwable
    • org.apache.catalina.core.StandardHostValve#status
    • 服务器异常处理先跳转到异常路径, 使用ErrorController处理
      • 一般情况下,都会生成HTML(如果没有要求JSON的话)因为RequestMappingInfo#compareTo的排序导致HTML为第一个
  • 抛出异常: spring的HandlerExceptionResolver处理
    • DispatcherServlet#processHandlerException
    • 如果解析不了,就会到服务器异常处理StandardHostValve#throwable
  • 总结
    • ErrorController通常会生成HTML
      • 可以自己写一个ErrorController从HttpServletRequest获取信息,使用response或者直接返回一个JSON对象
    • @ExceptionHandler:因为是自己处理的,所以想怎么样就怎么样

    • 对于sendError:
      • 一般自己不要用这个,而是用ExceptionHandler
      • 如果需要返回响应体,最好还是自己写ErrorController
        • 根据下面服务器异常处理的错误属性获取自己需要的值
    • 对于DispatchServlet的异常:
      • 如果不想让标准Servlet异常ServletException给DefaultHandlerExceptionResolver处理,或者ResponseStatusException@ResponseStatus给ResponseStatusExceptionResolver处理,导致sendError然后给ErrorController
        • 就使用 @ExceptionHandler捕获Exception或者捕获ServletException与ResponseStatusException
          • 或者说直接捕获Throwable,就会把所有其他异常都捕获起来了
        • 其中ModelAndViewDefiningException(ServletException)是解析不了的,会直接render视图
        • HttpSessionRequiredException:需要自己设置

例子

  • 最好还可以设置spring.mvc.throw-exception-if-no-handler-found=true,避免一个sendError内部转发到ErrorController(默认false,如果为true交给ExceptionHandler处理)
  • ErrorController一定要模仿BasicErrorController,路径是@RequestMapping({"{server.error.path:{error.path:/error}}"})(因为服务器处理的ErrorPage是这个)
  • 使用
    • 在下面的例子中
    • sendError:处理默认的spring和服务器的sendError/其他人的,ErrorController处理
      • 如果自己需要sendError(如果处理范围还在DispatchServlet中不推荐)
        • 推荐使用sendError(status, message),这样就会响应status=status, body={"message": message, code: status}
    • exceptionHandler:处理DispatchServlet中的所有异常,因此不会到服务器异常处理sendError(500)然后ErrorController中处理
      • 下面2个都使用了ExceptionResolverUtil进行解析(copy DefaultHandlerExceptionResolver和ResponseEntityExceptionHandler)
        • ServletException
        • ResponseStatusException: :如果仅仅简单是响应指定status,body={code=status, message=reason}可以直接抛出ResponseStatusException
      • 最常见的请求参数异常
        • spring的
          • BindException
          • ConstraintViolationException
          • MethodArgumentNotValidException
        • 自定义
          • BadRequestException
      • Throwable:拦截这个保证了不会到服务器异常处理
      • BusinessException:业务异常(比如数据库插入失败但未抛出异常)
      • RpcAPIException:远程调用异常

ErrorController

@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
@Slf4j
public class MErrorController implements ErrorController {
  /**
   *   ○ exception:请求中的异常的name
   *   ○ trace:抛出的异常中的trace
   *   ○ BindingResult中的错误信息
   *     ■ message(没有BindingResult就默认加请求的中错误信息或者"No message available")
   *     ■ errors
   *   ○ 标准的错误响应码(可能没有,可能有)
   *     ■ status:
   *     ■ error
   *   ○ path:javax.servlet.error.request_uri
   *   ○ timestamp:ErrorAttributes添加的
   *最好使用RequestDispatcher获取错误属性
   *  ○ status:一般只会在sendError时才会走这个
   *     ■ javax.servlet.error.status_code=response.getStatus()
   *     ■ javax.servlet.error.message=response.getMessage()
   *     ■ org.apache.catalina.core.DISPATCHER_REQUEST_PATH=errorPage.getLocation()
   *     ■ org.apache.catalina.core.DISPATCHER_TYPE=DispatcherType.ERROR
   *     ■ javax.servlet.error.servlet_name=wrapper.getName()
   *     ■ javax.servlet.error.request_uri=request.getRequestURI()
   *  ○ 如果是捕获到异常
   *     ■ doDispatch处理请求出现异常都会被捕获,在processDispatchResult中处理,processDispatchResult出现的异常也都会被捕获,
   *     ■ javax.servlet.error.status_code=500
   *     ■ javax.servlet.error.message=throwable.getMessage()
   *     ■ javax.servlet.error.exception=realError(如果是ServletException就是getRootCause())
   *     ■ javax.servlet.error.exception_type=realError.getClass()
   */
  @RequestMapping
  public ResponseEntity<BaseResp<String>> sendErrorHandler(HttpServletRequest request) {
    HttpStatus status = this.getStatus(request);
    String message = this.getMessage(request);
    Throwable exception = this.getException(request);
    if(exception != null){
      message = status.getReasonPhrase();
    }
    String requestUri = this.getRequestUri(request);
    if(StringUtils.hasText(requestUri)){
      message = "in ["+requestUri+"], " + message;
    }
    if (log.isDebugEnabled()) {
      log.debug("requestUri={}, HttpStatus={}, message={}",
          requestUri, status, this.getMessage(request),
          exception
      );
    }
    return new ResponseEntity<>(
        BaseResp.fail(message, status.value()),
        status
    );
  }

  public HttpStatus getStatus(HttpServletRequest req){
    return (HttpStatus)req.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
  }
  public String getMessage(HttpServletRequest req){
    return (String)req.getAttribute(RequestDispatcher.ERROR_MESSAGE);
  }
  public Throwable getException(HttpServletRequest req){
    return (Throwable)req.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
  }
  public String getRequestUri(HttpServletRequest req){
    return (String)req.getAttribute(RequestDispatcher.ERROR_REQUEST_URI);
  }
}

ExceptionHandler

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandlers {

  @ExceptionHandler({ServletException.class})
  public ResponseEntity<BaseResp<String>> servletExceptionHandler(HttpServletRequest req,
      HttpServletResponse resp, ServletException ex) {
    HttpStatus httpStatus = ExceptionResolverUtil.resolveServletException(req, resp, ex);
    if (httpStatus == null) {
      httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
    }
    if (log.isDebugEnabled()) {
      log.debug("servletExceptionHandler, httpStatus={}", httpStatus, ex);
    }
    return new ResponseEntity<>(
        BaseResp.fail(httpStatus.getReasonPhrase(), httpStatus.value()),
        httpStatus
    );
  }

  @ExceptionHandler({ResponseStatusException.class})
  public ResponseEntity<BaseResp<String>> responseStatusExceptionHandler(HttpServletRequest req,
      HttpServletResponse resp, ResponseStatusException ex) {
    StandardStatus standardStatus = ExceptionResolverUtil.resolveResponseStatusException(req, resp,
        ex);
    if (log.isDebugEnabled()) {
      log.debug("responseStatusExceptionHandler, StandardStatus={}", standardStatus, ex);
    }
    return new ResponseEntity<>(
        BaseResp.fail(standardStatus.reason, standardStatus.status.value()),
        standardStatus.status
    );
  }


  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler({
      BindException.class,
      ConstraintViolationException.class,
      MethodArgumentNotValidException.class,
      BadRequestException.class
  })
  public BaseResp<String> badRequestExceptionHandler(Exception err) {
    String errorMessages = "";
    if (err instanceof ConstraintViolationException) {
      ConstraintViolationException error = (ConstraintViolationException) err;
      errorMessages = error.getConstraintViolations().stream().map(ConstraintViolation::getMessage)
          .collect(Collectors.toList()).toString();
    } else if (err instanceof BindException) {
      BindException error = (BindException) err;
      errorMessages = error.getAllErrors().stream().map(
              DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.toList())
          .toString();
    } else if (err instanceof BadRequestException) {
      if (((BadRequestException) err).isLogged) {
        return BaseResp.fail(err.getMessage(), ErrorCode.PARAMETER);
      }
    }
    if (log.isDebugEnabled()) {
      log.debug("badRequestExceptionHandler, ErrorCode={}", ErrorCode.PARAMETER, err);
    }
    return BaseResp.fail(errorMessages, ErrorCode.PARAMETER);
  }

  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  @ExceptionHandler({Throwable.class})
  public BaseResp<String> systemExceptionHandler(Throwable err) {
    if (err.getCause() != null) {
      err = err.getCause();
    }
    if (err instanceof LogException) {
      LogException logErr = (LogException) err;
      if (logErr.isLogged) {
        return BaseResp.fail(ErrorCode.SYSTEM);
      }
    }
    if (log.isDebugEnabled()) {
      log.debug("systemExceptionHandler, ErrorCode={}", ErrorCode.SYSTEM, err);
    }
    return BaseResp.fail(ErrorCode.SYSTEM);
  }


  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  @ExceptionHandler({BusinessException.class})
  public BaseResp<String> businessErrorHandler(BusinessException err) {
    if (!err.isLogged) {
      if (log.isDebugEnabled()) {
        log.debug("businessErrorHandler, ErrorCode={}", ErrorCode.SYSTEM, err);
      }
    }
    if (err.getErrorCode() != null) {
      return BaseResp.fail(err.getErrorCode());
    }
    return BaseResp.fail(err.getMessage(), ErrorCode.SYSTEM);
  }

  @ExceptionHandler({RpcAPIException.class})
  public ResponseEntity<BaseResp<String>> rpcAPIExceptionHandler(RpcAPIException err) {
    if (!err.isLogged) {
      if (log.isDebugEnabled()) {
        log.debug("rpcAPIExceptionHandler, ErrorCode={}", ErrorCode.RPC_API, err);
      }
    }
    return ResponseEntity.status(err.status())
        .body(BaseResp.fail(err.getMessage(), ErrorCode.RPC_API));
  }
}

辅助

public class LogException extends RuntimeException{
  public final boolean isLogged;
  public LogException(String message) {
    super(message);
    this.isLogged = false;
  }
  public LogException(boolean isLogged, String message) {
    super(message);
    this.isLogged = isLogged;
  }
}
@AllArgsConstructor
public enum ErrorCode {
    OK("ok", 0),
    PARAMETER("err parameters", 400),
    SYSTEM("system err", 500),
    RPC_API("服务异常", 5101),
    NOT_FOUND_USER("service isn't usable", 5201);
    public final String message;
    public final Integer code;
}
@Data
public class BaseResp<T> {

  private T data;
  private String message;
  private Integer code;

  public BaseResp() {
  }

  public BaseResp(T data, Integer code) {
    this.data = data;
    this.code = code;
  }

  public BaseResp(String message, Integer code) {
    this.message = message;
    this.code = code;
  }

  public BaseResp(ErrorCode errorCode) {
    this.message = errorCode.message;
    this.code = errorCode.code;
  }

  public static <D> BaseResp<D> ok(D data) {
    return new BaseResp<>(data, ErrorCode.OK.code);
  }

  public static <D> BaseResp<D> fail(String message, ErrorCode errorCode) {
    return new BaseResp<>(message, errorCode.code);
  }

  public static <D> BaseResp<D> fail(ErrorCode errorCode) {
    return new BaseResp<>(errorCode.message, errorCode.code);
  }

  public static <D> BaseResp<D> fail(String message, int errorCode) {
    return new BaseResp<>(message, errorCode);
  }


  /**
   * @param supplier rpc调用
   * @param message  调用处的上下文, 如果有异常会把rpcAPI异常追加到后面
   * @return
   */
  public static <T> T withCatch(Supplier<BaseResp<T>> supplier, String message) {
    BaseResp<T> baseResp = null;
    try {
      baseResp = supplier.get();
      if (ErrorCode.OK.code.equals(baseResp.code)) {
        return baseResp.data;
      } else {
        throw new RpcAPIException(message + ", " + baseResp.message);
      }
    } catch (FeignException e) {
      throw new RpcAPIException(
          baseResp != null ? message + "," + baseResp.message : message,
          HttpStatus.resolve(e.status())
      );
    }
  }

  /**
   * @param supplier rpc调用
   * @param logFunc  表示进行打印详细的日志, 全局异常处理就不会进行打印日志
   * @param message  调用处的上下文, 如果有异常会把rpcAPI异常追加到后面
   */
  public static <T> T withCatch(Supplier<BaseResp<T>> supplier,
      BiFunction<BaseResp<T>, FeignException, T> logFunc, String message) {
    BaseResp<T> baseResp = null;
    try {
      baseResp = supplier.get();
      if (ErrorCode.OK.code.equals(baseResp.code)) {
        return baseResp.data;
      } else {
        logFunc.apply(baseResp, null);
        throw new RpcAPIException(true, message + ", " + baseResp.message);
      }
    } catch (FeignException e) {
      logFunc.apply(baseResp, e);
      throw new RpcAPIException(true,
          baseResp != null ? message + "," + baseResp.message : message,
          HttpStatus.resolve(e.status())
      );
    }
  }
}
public class StandardStatus {

  public final HttpStatus status;
  public final String reason;

  public StandardStatus(HttpStatus status, String reason) {
    this.status = status;
    if(StringUtils.hasText(reason)){
      this.reason = reason;
    }else{
      this.reason = status.getReasonPhrase();
    }
  }
}

public class ExceptionResolverUtil {
  public static StandardStatus resolveResponseStatusException(HttpServletRequest request, HttpServletResponse response, ResponseStatusException ex) {
    ex.getResponseHeaders().forEach((name, values) -> {
      values.forEach((value) -> {
        response.addHeader(name, value);
      });
    });
    return new StandardStatus(ex.getStatus(), ex.getReason());
  }
  public static HttpStatus resolveServletException(HttpServletRequest request, HttpServletResponse response, Exception ex){
    if (ex instanceof HttpRequestMethodNotSupportedException) {
      String[] supportedMethods =((HttpRequestMethodNotSupportedException) ex).getSupportedMethods();
      if (supportedMethods != null) {
        response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));
      }
      return HttpStatus.METHOD_NOT_ALLOWED;
    }

    if (ex instanceof HttpMediaTypeNotSupportedException) {
      List<MediaType> mediaTypes = ((HttpMediaTypeNotSupportedException)ex).getSupportedMediaTypes();
      if (!CollectionUtils.isEmpty(mediaTypes)) {
        response.setHeader("Accept", MediaType.toString(mediaTypes));
        if (request.getMethod().equals("PATCH")) {
          response.setHeader("Accept-Patch", MediaType.toString(mediaTypes));
        }
      }
      return HttpStatus.UNSUPPORTED_MEDIA_TYPE;
    }

    if (ex instanceof HttpMediaTypeNotAcceptableException) {
      return HttpStatus.NOT_ACCEPTABLE;
    }

    if (ex instanceof MissingPathVariableException) {
      return HttpStatus.INTERNAL_SERVER_ERROR;
    }

    if (ex instanceof MissingServletRequestParameterException) {
      return HttpStatus.BAD_REQUEST;
    }

    if (ex instanceof ServletRequestBindingException) {
      return HttpStatus.BAD_REQUEST;
    }

    if (ex instanceof ConversionNotSupportedException) {
      return HttpStatus.INTERNAL_SERVER_ERROR;    }

    if (ex instanceof TypeMismatchException) {
      return HttpStatus.BAD_REQUEST;    }

    if (ex instanceof HttpMessageNotReadableException) {
      return HttpStatus.BAD_REQUEST;    }

    if (ex instanceof HttpMessageNotWritableException) {
      return HttpStatus.INTERNAL_SERVER_ERROR;     }


    if (ex instanceof MissingServletRequestPartException) {
      return HttpStatus.BAD_REQUEST;      }


    if (ex instanceof NoHandlerFoundException) {
      return HttpStatus.NOT_FOUND;      }

    if (ex instanceof AsyncRequestTimeoutException) {
      return HttpStatus.SERVICE_UNAVAILABLE;
    }
    if (ex instanceof HttpSessionRequiredException) {
      return HttpStatus.BAD_REQUEST;
    }

    return null;
  }
}

服务器的异常处理sendError

  • 注意getHandler找不到,即没有对应path
    • 执行noHandlerFound:throwExceptionIfNoHandlerFound(默认false)=true就抛出NoHandlerFoundException,否则response.sendError(404)
      • 这个比较特殊,因为连路径都匹配不到
  • 可能还有其他的,会直接sendError,比如ResourceHttpRequestHandler
  • sendError这些是捕获不了异常的,是tomcat自己处理的,但是spring自己重写的内嵌tomcat,自己处理了响应异常 (要注意有2个sendError,有一个是带message的)
    • 在org.apache.catalina.core.StandardHostValve#invoke:做完context.getPipeline().getFirst().invoke(request, response);后
    • 不管是有异常被设置javax.servlet.error.exception(这些错误一般是doDispatch没有进行捕获的),还是status是异常码,都是request.getContext()(TomcatEmbeddedContext)来处理
      • context.findErrorPage(throwable):org.apache.catalina.core.StandardHostValve#throwable
      • context.findErrorPage(statusCode):org.apache.catalina.core.StandardHostValve#status
      • 返回一个ErrorPage(一般是/error,是在)
      • 找ErrorPage,设置request属性,继续调用org.apache.catalina.core.StandardHostValve#custom
        • 从ServletContext获取RequestDispatcher(DispatcherType.ERROR)
        • 如果response.isCommitted()就include,否则forward
  • sendError是服务器的异常处理,spring有提供对服务器tomcat等的错误处理代替,由ErrorPageCustomizer实现

  • 服务器设置的错误属性 (全在RequestDispatcher中)
    • status:一般只会在sendError时才会走这个
      • javax.servlet.error.status_code=response.getStatus()
      • javax.servlet.error.message=response.getMessage()
      • org.apache.catalina.core.DISPATCHER_REQUEST_PATH=errorPage.getLocation()
      • org.apache.catalina.core.DISPATCHER_TYPE=DispatcherType.ERROR
      • javax.servlet.error.servlet_name=wrapper.getName()
      • javax.servlet.error.request_uri=request.getRequestURI()
    • 如果是捕获到异常
      • doDispatch处理请求出现异常都会被捕获,在processDispatchResult中处理,processDispatchResult出现的异常也都会被捕获,
      • javax.servlet.error.status_code=500
      • javax.servlet.error.message=throwable.getMessage()
      • javax.servlet.error.exception=realError(如果是ServletException就是getRootCause())
      • javax.servlet.error.exception_type=realError.getClass()
    • 如果是异常,在spring mvc中会先在DispatcherServlet#processHandlerException中使用HandlerExceptionResolver处理
      • 其中DefaultErrorAttributes不做什么,就做设置请求属性org.springframework.boot.web.servlet.error.DefaultErrorAttributes.ERROR=ex

processHandlerException

  • 用于DispatchServlet#processHandlerException方法
    • 遍历HandlerExceptionResolvers返回一个ModelAndView
    • DefaultErrorAttributes仅仅是保存异常到org.springframework.boot.web.servlet.error.DefaultErrorAttributes.ERROR,以便在BasicErrorController中获取exception(这个因为在DispatchServlet有异常,因此)

配置

  • 在DispatchServlet初始化时,会得到2个HandlerExceptionResolver
    • DefaultErrorAttributes:优先级最低的(低的排第一个)
    • HandlerExceptionResolverComposite

EnableWebMvcConfiguration

  • HandlerExceptionResolver的生成
    • 在EnableWebMvcConfiguration(扩展配置)的父类WebMvcConfigurationSupport方法handlerExceptionResolver
      • addDefaultHandlerExceptionResolvers(保存在HandlerExceptionResolverComposite)
        • 创建ExceptionHandlerExceptionResolver(如果没有这个bean)
          • @ExceptionHandler注解来处理
          • 如果是在 @ControllerAdvice中,那么就会处理所有Controller
          • @ExceptionHandler的方法就和普通的handler一样执行(HandlerMethod)因此@ResponseStatus也同样作用于@ExceptionHandler的方法
        • ResponseStatusExceptionResolver
          • ResponseStatusException:标准状态码异常
          • @ResponseStatus的异常
          • 如果还没解析到,且有Cause就继续解析Cause
          • 最后也是进行sendError
        • DefaultHandlerExceptionResolver
          • 标准状态码的对应的异常处理ServletException,会对对应的status进行sendError

ResponseEntityExceptionHandler

  • 个人认为(没太大用处)
    • DefaultHandlerExceptionResolver是用于生成HTML用的,是直接sendError,然后转发到/error端点的生成HTML
    • ResponseEntityExceptionHandler是用于给 @ControllerAdvice的全局异常处理类继承来方便开发者处理ServletException,TypeMismatchException,HttpMessageConversionException,NestedRuntimeException,BindException,AsyncRequestTimeoutException等这些异常
      • 注意,如果都不是会抛出,但是不可能的,只是为了代码不报错而已
      • 但只是简单处理了,都没有设置body,最终都会调用handleExceptionInternal返回一个ResponseEntity
        • 通常都不需要body,但对于参数异常,可能需要自己返回body,所以可以自定义
private final static ResponseEntityExceptionHandler responseEntityExceptionHandler = new ResponseEntityExceptionHandler() {};
@ExceptionHandler({HttpRequestMethodNotSupportedException.class, HttpMediaTypeNotSupportedException.class, HttpMediaTypeNotAcceptableException.class, MissingPathVariableException.class, MissingServletRequestParameterException.class, ServletRequestBindingException.class, ConversionNotSupportedException.class, TypeMismatchException.class, HttpMessageNotReadableException.class, HttpMessageNotWritableException.class, MissingServletRequestPartException.class, NoHandlerFoundException.class, AsyncRequestTimeoutException.class})
    public ResponseEntity<Object> standardResponseExceptionHandler(Exception ex, WebRequest request) {
    try {
        return responseEntityExceptionHandler.handleException(ex, request);
    } catch (Exception e) {
        return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }
}


@ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({BindException.class, ConstraintViolationException.class,
                       MethodArgumentNotValidException.class})
    public BaseResp<String> validateErr(Throwable err) {
    if (log.isDebugEnabled()) {
    log.debug("validateErr, ErrorCode={}", ErrorCode.PARAMETER, err);
}
List<String> errorMessages = null;
if (err instanceof ConstraintViolationException) {
    ConstraintViolationException error = (ConstraintViolationException) err;
    errorMessages = error.getConstraintViolations().stream().map(ConstraintViolation::getMessage)
        .collect(Collectors.toList());
} else if (err instanceof BindException) {
    BindException error = (BindException) err;
    errorMessages = error.getAllErrors().stream().map(
        DefaultMessageSourceResolvable::getDefaultMessage).collect(Collectors.toList());
}
if(errorMessages == null){
    errorMessages = Collections.emptyList();
}
return BaseResp.fail(errorMessages.toString(), ErrorCode.PARAMETER);
}

ErrorMvcAutoConfiguration

  • 与一般HandlerExceptionResolver不同,是单独的controller处理异常,是内部转发操作
    • 其HandlerExceptionResolver是DefaultErrorAttributes
  • DefaultErrorViewResolverConfiguration
    • 解析error/+status的View
      • 一种是TemplateAvailabilityProvider即引擎解析
      • 否则是在配置的StaticLocations中找
  • WhitelabelErrorViewConfiguration
    • StaticView(如果没有error这个bean)即errorView
    • BeanNameViewResolver(因为error这个bean是一个View)

ErrorPageCustomizer

  • ErrorPageRegistry:注册了一个org.springframework.boot.autoconfigure.web.ErrorProperties#path路径的ErrorPage(默认 /error
    • 在server.error中配置
server:
  error:
    path: /error/arr #error路径,默认/error
    #on_params表示请求不带参数时才生效--queryString,form-data,x-www-form-urlencoded
    include-binding-errors: always  #error
    include-exception: on  #exception
    include-message: always #message
    include-stacktrace: always  #trace
    whitelabel:
      enabled: true
    • 然后添加到一个ErrorPageRegistrar(服务器的错误处理),比如TomcatServletWebServerFactory
    • 有专门的ErrorPageRegistrarBeanPostProcessor( ErrorMvcAutoConfiguration已经创建了ErrorPageRegistrar 在一种服务器启动时给服务器注册ErrorPage

BasicErrorController

  • 当前没有ErrorController才会创建这个
  • ErrorAttributes
  • ErrorViewResolver
  • path={server.error.path:{error.path:/error}}
    • 一个是浏览器的请求,会返回HTML
      • ErrorAttributes来解析请求中的设置的错误信息属性,用于设置在View中
      • 先使用ErrorViewResolver解析,没有就使用默认的ModelAndView("error", model)(即WhitelabelErrorViewConfiguration配置的error这个bean View)
    • 一个是其他端的,返回JSON
      • ErrorAttributes来解析请求中的设置的错误信息属性作为body
  • 错误信息
    • exception:请求中的异常的name
    • trace:抛出的异常中的trace
    • BindingResult中的错误信息
      • message(没有BindingResult就默认加请求的中错误信息或者"No message available")
      • errors
    • 标准的错误响应码(可能没有,可能有)
      • status:
      • error
    • path:javax.servlet.error.request_uri
    • timestamp:ErrorAttributes添加的