总结
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
-
-
- 如果不想让标准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
-
-
-
-
- BindException
- ConstraintViolationException
- MethodArgumentNotValidException
-
-
- Throwable:拦截这个保证了不会到服务器异常处理
- BusinessException:业务异常(比如数据库插入失败但未抛出异常)
- RpcAPIException:远程调用异常
ErrorController
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
@Slf4j
public class MErrorController implements ErrorController {
@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);
}
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())
);
}
}
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
-
-
- 一种是TemplateAvailabilityProvider即引擎解析
- 否则是在配置的StaticLocations中找
- WhitelabelErrorViewConfiguration
-
- StaticView(如果没有error这个bean)即errorView
- BeanNameViewResolver(因为error这个bean是一个View)
ErrorPageCustomizer
- ErrorPageRegistry:注册了一个org.springframework.boot.autoconfigure.web.ErrorProperties#path路径的ErrorPage(默认 /error)
server:
error:
path: /error/arr
include-binding-errors: always
include-exception: on
include-message: always
include-stacktrace: always
whitelabel:
enabled: true
-
- 然后添加到一个ErrorPageRegistrar(服务器的错误处理),比如TomcatServletWebServerFactory
- 有专门的ErrorPageRegistrarBeanPostProcessor( ErrorMvcAutoConfiguration已经创建了ErrorPageRegistrar ) 在一种服务器启动时给服务器注册ErrorPage
BasicErrorController
- 当前没有ErrorController才会创建这个
- ErrorAttributes
- ErrorViewResolver
- path={server.error.path:{error.path:/error}}
-
-
- ErrorAttributes来解析请求中的设置的错误信息属性,用于设置在View中
- 先使用ErrorViewResolver解析,没有就使用默认的ModelAndView("error", model)(即WhitelabelErrorViewConfiguration配置的error这个bean View)
-
-
- ErrorAttributes来解析请求中的设置的错误信息属性作为body
-
- exception:请求中的异常的name
- trace:抛出的异常中的trace
- BindingResult中的错误信息
-
-
- message(没有BindingResult就默认加请求的中错误信息或者"No message available")
- errors
-
- path:javax.servlet.error.request_uri
- timestamp:ErrorAttributes添加的