Java 重写标准Spring MVC异常的处理行为

Java 重写标准Spring MVC异常的处理行为,java,spring,spring-mvc,spring-boot,exception-handling,Java,Spring,Spring Mvc,Spring Boot,Exception Handling,SpringBoot似乎具有处理某些异常的默认行为。 我有一个休息控制器。如果我在@ControllerAdvice注释的rest控制器中不处理HttpRequestMethodNotSupportedException,应用程序将返回包含错误消息的默认JSON响应 我不想替换这个JSON响应,但我确实想在它发生时记录其他信息(例如,记录某些请求者的IP地址) 有没有一种方法可以通过@ExceptionHandler带注释的方法或其他机制来实现这一点?Spring MVC确实为您配置了一个异常处

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

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


有没有一种方法可以通过
@ExceptionHandler
带注释的方法或其他机制来实现这一点?

Spring MVC确实为您配置了一个异常处理程序。
默认情况下,按照类javadoc中的说明使用:

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

默认情况下,此异常解析程序在公共Spring中启用
org.springframework.web.servlet.DispatcherServlet

这对于MVC控制器来说是正确的

但是对于REST控制器的异常处理程序(您在这里的需求),Spring依赖于
ResponseEntityExceptionHandler
类 第一个类有返回
ModelAndView
s的方法,而第二个类有返回
ReponseEntity
s的方法

在这两种情况下(MVC和REST控制器),您都可以通过使用
@ControllerAdvice
注释类来定义自定义异常处理程序,但由于您的需求是针对REST控制器的,所以让我们重点关注这一点

除了用
@ControllerAdvice
注释自定义异常处理程序外,您还可以这样做以扩展基本异常处理程序类,例如重写某些行为。
ResponseEntityExceptionHandler
实现允许了解实际处理和映射的所有异常。查看
handleException()
方法,它是
ResponseEntityExceptionHandler
类的facade方法:

/**
 * 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);
    }
}   
为了简化客户端子类覆盖特定异常的实际处理/映射,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);
    }
}
@ControllerAdvice
公共类MyExceptionHandler扩展了ResponseEntityExceptionHandler{
@凌驾
受保护的响应handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,HttpHeaders标头,HttpStatus状态,
WebRequest请求){
//做你的处理
...
//继续(或否)执行基类中定义的逻辑
返回super.handleHttpRequestMethodNotSupported(例如,标题、状态、请求);
}
}

感谢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);
    }
}
@ControllerAdvice
公共类AppExceptionHandler扩展了ResponseEntityExceptionHandler{
@ExceptionHandler(ResourceNotFoundException.class)
公共响应属性resourceNotFoundException(最终resourceNotFoundException ex,最终WebRequest请求){
final ErrorDetails ErrorDetails=新的ErrorDetails(新日期(),例如getMessage(),request.getDescription(false));
返回新的响应属性(errorDetails,HttpStatus.NOT_FOUND);
}
@ExceptionHandler(ResourceReadyExistsfoundException.class)
公共响应属性ResourceReadyExistsFundException(最终ResourceReadyExistsFundException ex,最终WebRequest请求){
final ErrorDetails ErrorDetails=新的ErrorDetails(新日期(),例如getMessage(),request.getDescription(false));
返回新的响应属性(errorDetails,HttpStatus.BAD_请求);
}
@ExceptionHandler(ConstraintViolationException.class)
对ConstraintValidationException的响应(
约束(异常){
列表错误=新建ArrayList();
for(ConstraintViolations冲突:e.getConstraintViolations()){
错误。添加(
新的错误详细信息(新日期(),违规。getPropertyPath()。toString(),违规。getMessage());
}
返回新的响应属性(错误,HttpStatus.BAD_请求);
}
@凌驾
受保护的ResponseEntity handleMethodArgumentNotValid无效(MethodArgumentNotValidException ex、HttpHeaders标头、HttpStatus状态、WebRequest请求){
ArrayList errors=新建ArrayList();
对于(FieldError FieldError:ex.getBindingResult().getFieldErrors()){
错误。添加(
新的ErrorDetails(新日期(),fieldError.getField(),fieldError.getDefaultMessage());
}
返回新的响应属性(错误,HttpStatus.BAD_请求);
}
@ExceptionHandler(Exception.class)
public ResponseEntity globleExcpetionHandler(最终异常示例,最终WebRequest请求){
final ErrorDetails ErrorDetails=新的ErrorDetails(新日期(),例如getMessage(),request.getDescription(false));
返回新的响应属性(errorDetails,HttpStatus.INTERNAL_SERVER_ERROR);
}
}

您的意思是希望自定义JSON响应,还是在发生这种情况时只需要额外的日志记录?重写方法适用于HandleHttpMessageNodeTable方法,但不适用于ResponseEntityExceptionHandler类中的其他方法(例如:handleNoHandlerFoundException)。知道为什么会发生这种情况吗?
@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);
    }
}
    @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);
    }
}