覆盖标准 Spring MVC 异常的处理行为



Spring Boot 似乎具有处理某些异常的默认行为。 我有一个休息控制器。如果我不处理@ControllerAdvice带批注的 rest 控制器中的HttpRequestMethodNotSupportedException,应用程序将返回包含错误消息的默认 JSON 响应。

我不想替换此 JSON 响应,但我确实想在发生时记录其他信息(例如.log某个请求者的 IP 地址(。

有没有办法使用@ExceptionHandler注释方法或其他机制来做到这一点?

Spring MVC 确实为你配置了一个异常处理程序。
默认情况下,DefaultHandlerExceptionResolver的使用方式如 javadoc 类中所述:

HandlerExceptionResolver接口的默认实现 解决标准的 Spring 异常并将其转换为 相应的 HTTP 状态代码。

默认情况下,此异常解析器在公共 Spring 中处于启用状态org.springframework.web.servlet.DispatcherServlet.
这适用于 MVC 控制器。

但是对于 REST 控制器的异常处理程序(您在此处的要求(,Spring 依赖于ResponseEntityExceptionHandler类.
第一个类具有返回ModelAndViews 的方法,而第二个类具有返回ReponseEntitys 的方法。

在这两种情况下(MVC 和 REST 控制器(,您可以通过使用@ControllerAdvice注释您的类来定义自定义异常处理程序,但由于您的要求是针对 REST 控制器的,因此让我们专注于此。

除了使用@ControllerAdvice注释自定义异常处理程序之外,您还可以这样做来扩展基本异常处理程序类(如ResponseEntityExceptionHandler(以覆盖某些行为。
ResponseEntityExceptionHandler实现允许知道实际处理和映射的所有异常。 看看handleException()方法,即ResponseEntityExceptionHandler类的立面方法:

/**
* Provides handling for standard Spring MVC exceptions.
* @param ex the target exception
* @param request the current request
*/
@ExceptionHandler({
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
HttpMediaTypeNotAcceptableException.class,
MissingPathVariableException.class,
MissingServletRequestParameterException.class,
ServletRequestBindingException.class,
ConversionNotSupportedException.class,
TypeMismatchException.class,
HttpMessageNotReadableException.class,
HttpMessageNotWritableException.class,
MethodArgumentNotValidException.class,
MissingServletRequestPartException.class,
BindException.class,
NoHandlerFoundException.class,
AsyncRequestTimeoutException.class
})
@Nullable
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) {
HttpHeaders headers = new HttpHeaders();
if (ex instanceof HttpRequestMethodNotSupportedException) {
HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED;
return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request);
}
else if (ex instanceof HttpMediaTypeNotSupportedException) {
HttpStatus status = HttpStatus.UNSUPPORTED_MEDIA_TYPE;
return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, headers, status, request);
}
else if (ex instanceof HttpMediaTypeNotAcceptableException) {
HttpStatus status = HttpStatus.NOT_ACCEPTABLE;
return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, headers, status, request);
}
else if (ex instanceof MissingPathVariableException) {
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
return handleMissingPathVariable((MissingPathVariableException) ex, headers, status, request);
}
else if (ex instanceof MissingServletRequestParameterException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, headers, status, request);
}
else if (ex instanceof ServletRequestBindingException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleServletRequestBindingException((ServletRequestBindingException) ex, headers, status, request);
}
else if (ex instanceof ConversionNotSupportedException) {
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
return handleConversionNotSupported((ConversionNotSupportedException) ex, headers, status, request);
}
else if (ex instanceof TypeMismatchException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleTypeMismatch((TypeMismatchException) ex, headers, status, request);
}
else if (ex instanceof HttpMessageNotReadableException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, headers, status, request);
}
else if (ex instanceof HttpMessageNotWritableException) {
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, headers, status, request);
}
else if (ex instanceof MethodArgumentNotValidException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleMethodArgumentNotValid((MethodArgumentNotValidException) ex, headers, status, request);
}
else if (ex instanceof MissingServletRequestPartException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleMissingServletRequestPart((MissingServletRequestPartException) ex, headers, status, request);
}
else if (ex instanceof BindException) {
HttpStatus status = HttpStatus.BAD_REQUEST;
return handleBindException((BindException) ex, headers, status, request);
}
else if (ex instanceof NoHandlerFoundException) {
HttpStatus status = HttpStatus.NOT_FOUND;
return handleNoHandlerFoundException((NoHandlerFoundException) ex, headers, status, request);
}
else if (ex instanceof AsyncRequestTimeoutException) {
HttpStatus status = HttpStatus.SERVICE_UNAVAILABLE;
return handleAsyncRequestTimeoutException(
(AsyncRequestTimeoutException) ex, headers, status, request);
}
else {
if (logger.isWarnEnabled()) {
logger.warn("Unknown exception type: " + ex.getClass().getName());
}
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
return handleExceptionInternal(ex, null, headers, status, request);
}
}   

所以问题是:如何覆盖特定异常的异常处理程序?
这种方法不起作用:

@ExceptionHandler(value = { HttpRequestMethodNotSupportedException.class })
protected ResponseEntity<Object> handleConflict(HttpRequestMethodNotSupportedException ex, WebRequest request) {
...
}

因为在异常处理程序类中,Spring 不允许你多次定义特定Exception子类的映射。 因此,不允许在自定义异常处理程序中添加此映射,因为 Spring 已经在ResponseEntityExceptionHandler类中为该异常定义了映射。
具体来说,它将阻止 Spring 容器成功启动。
您应该得到一个异常,例如:

原因:java.lang.IllegalStateException: Amambious @ExceptionHandler 方法 映射为 [class org.springframework.web.HttpRequestMethodNotSupportedException]: {protected org.springframework...

为了简化客户端子类覆盖特定异常的实际处理/映射,Spring 在ResponseEntityExceptionHandler类的protected方法中实现了每个异常的逻辑,
因此在您的情况下(覆盖HttpRequestMethodNotSupportedException的处理程序(,只需覆盖您正在寻找的handleHttpRequestMethodNotSupported()

if (ex instanceof HttpRequestMethodNotSupportedException) {
HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED;
return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request);
}

例如这样:

@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
@Override
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status,
WebRequest request) {    
// do your processing
...
// go on (or no) executing the logic defined in the base class 
return super.handleHttpRequestMethodNotSupported(ex, headers, status, request);
}
}

感谢 davidxxx 的回答和例子。他们帮了我很多。谢谢! 我这样做了,它对我有用。下面是一个示例:

@ControllerAdvice
public class AppExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<?> resourceNotFoundException(final ResourceNotFoundException ex, final WebRequest request) {
final ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(ResourceAlreadyExistsFoundException.class)
public ResponseEntity<?> resourceAlreadyExistsFoundException(final ResourceAlreadyExistsFoundException ex, final WebRequest request) {
final ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(ConstraintViolationException.class)
ResponseEntity<?> onConstraintValidationException(
ConstraintViolationException e) {
List<ErrorDetails> errors = new ArrayList<>();
for (ConstraintViolation violation : e.getConstraintViolations()) {
errors.add(
new ErrorDetails(new Date(), violation.getPropertyPath().toString(), violation.getMessage()));
}
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
ArrayList<ErrorDetails> errors = new ArrayList<>();
for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) {
errors.add(
new ErrorDetails(new Date(), fieldError.getField(), fieldError.getDefaultMessage()));
}
return new ResponseEntity<>(errors, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<?> globleExcpetionHandler(final Exception ex, final WebRequest request) {
final ErrorDetails errorDetails = new ErrorDetails(new Date(), ex.getMessage(), request.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
}
}

最新更新