为[class org.springframework.web.bind.MethodArgumentNotValidException]映射了模糊的@ExceptionHandler方法

82

我想使用 @ControllerAdvice 处理 MethodArgumentNotValidException 异常,代码如下:

@ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
    
    private Logger log = LoggerFactory.getLogger(RestResponseEntityExceptionHandler.class);
    
    @Autowired
    private ApplicationContext applicationContext;
    
    @ExceptionHandler({ ConstraintViolationException.class })
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ErrorWrapper handleConstraintViolationException(ConstraintViolationException e) {
        String fieldName = e.getConstraintName();
        String message = getResourceMessage(fieldName + ".alreadyExists", "Already Exists");
        return new ErrorWrapper(fieldName + ".error", message);
    }
    
    @ExceptionHandler({ MethodArgumentNotValidException.class })
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public ErrorWrapper handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        return new ErrorWrapper(".error", "test");
    }
    
    private String getResourceMessage(String key, String defaultMessage) {
        String message = applicationContext.getMessage(key, null, Locale.getDefault());
        if (StringUtils.isNotEmpty(message)) {
            return message;
        }
        return defaultMessage;
    }
}

我遇到了以下异常

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0': Invocation of init method failed; nested exception is java.lang.IllegalStateException: Ambiguous @ExceptionHandler method mapped for [class org.springframework.web.bind.MethodArgumentNotValidException]: {public com.ca.bean.ErrorWrapper com.ca.exceptionHandler.RestResponseEntityExceptionHandler.handleMethodArgumentNotValidException(org.springframework.web.bind.MethodArgumentNotValidException), public final org.springframework.http.ResponseEntity org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler.handleException(java.lang.Exception,org.springframework.web.context.request.WebRequest)}
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1578) ~[spring-beans-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:545) ~[spring-beans-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482) ~[spring-beans-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:775) ~[spring-beans-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:861) ~[spring-context-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:541) ~[spring-context-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:444) ~[spring-web-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:326) ~[spring-web-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:107) [spring-web-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:5099) [catalina.jar:7.0.70]
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5615) [catalina.jar:7.0.70]
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:147) [catalina.jar:7.0.70]
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1571) [catalina.jar:7.0.70]
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1561) [catalina.jar:7.0.70]
    at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_92]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [na:1.8.0_92]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [na:1.8.0_92]
    at java.lang.Thread.run(Thread.java:745) [na:1.8.0_92]
Caused by: java.lang.IllegalStateException: Ambiguous @ExceptionHandler method mapped for [class org.springframework.web.bind.MethodArgumentNotValidException]: {public com.ca.bean.ErrorWrapper com.ca.exceptionHandler.RestResponseEntityExceptionHandler.handleMethodArgumentNotValidException(org.springframework.web.bind.MethodArgumentNotValidException), public final org.springframework.http.ResponseEntity org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler.handleException(java.lang.Exception,org.springframework.web.context.request.WebRequest)}
    at org.springframework.web.method.annotation.ExceptionHandlerMethodResolver.addExceptionMapping(ExceptionHandlerMethodResolver.java:109) ~[spring-web-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.web.method.annotation.ExceptionHandlerMethodResolver.<init>(ExceptionHandlerMethodResolver.java:76) ~[spring-web-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver.initExceptionHandlerAdviceCache(ExceptionHandlerExceptionResolver.java:265) ~[spring-webmvc-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver.afterPropertiesSet(ExceptionHandlerExceptionResolver.java:241) ~[spring-webmvc-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1637) ~[spring-beans-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574) ~[spring-beans-4.3.0.RELEASE.jar:4.3.0.RELEASE]
    ... 21 common frames omitted

我正在使用Spring 4.3.0。 我想处理MethodArgumentNotValidException并发送自定义消息。 我犯了什么错误? 请帮帮我。


可能对其他人有用。请参阅:https://howtodoinjava.com/spring-core/spring-exceptionhandler-annotation/ - Akki
你好,问题已经修复了,希望能对你有所帮助。https://dev59.com/BlQK5IYBdhLWcg3wXeuM#74552716 - smhylc
1
从你的类中删除 extends ResponseEntityExceptionHandler,你就不会再遇到模糊的 @ExceptionHandler 问题了。 - tonnoz
8个回答

142

Spring的ResponseEntityExceptionHandler类有一个handleException方法,该方法使用注解:

@ExceptionHandler({     
        ...
        MethodArgumentNotValidException.class,
        ... 
    })

您的方法handleMethodArgumentNotValidException也被标记为处理MethodArgumentNotValidException。因此,Spring 找到了两个应该用于处理同一个异常的方法,这就是出现异常的原因。

**解决方案** 不要添加新方法handleMethodArgumentNotValidException,而是重写方法ResponseEntityExceptionHandler.handleMethodArgumentNotValid,并且不要对其进行注释。 您的类ErrorWrapper必须扩展ResponseEntity。


13
handleException方法是一个final方法,因此我们无法覆盖它。但该方法使用instanceof检查MethodArgumentNotValidException并调用名称为handleMethodArgumentNotValid的方法。因此,我尝试重写handleMethodArgumentNotValid方法,并且它起作用了。非常感谢。 - Krishna
1
@Krishna,你能展示一下你是如何解决这个问题的吗? - Dimitri Kopriwa
@BigDong...我需要看到目前我没有的代码。我会检查并提供代码,但需要9-10小时的时间。 - Krishna
还有没有@Order(9000)的解决方案? - Daniil Iaitskov
哦,我明白了。配置来自抽象的父类。没想到会这样。所以如果没有这个建议,“默认映射器”都不起作用。 - Daniil Iaitskov
显示剩余2条评论

20

处理异常

@RestControllerAdvice
@Slf4j
public class ExceptionHandler extends ResponseEntityExceptionHandler {
    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        List<String> errorList = ex
                .getBindingResult()
                .getFieldErrors()
                .stream()
                .map(fieldError -> fieldError.getDefaultMessage())
                .collect(Collectors.toList());
        ErrorDetails errorDetails = new ErrorDetails(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), errorList);
        return handleExceptionInternal(ex, errorDetails, headers, errorDetails.getStatus(), request);
    }
}

自定义错误消息

@Data
public class ErrorDetails {
    private HttpStatus status;
    private String message;
    private List<String> errors;

    public ErrorDetails(HttpStatus status, String message, List<String> errors) {
        super();
        this.status = status;
        this.message = message;
        this.errors = errors;
    }

    public ErrorDetails(HttpStatus status, String message, String error) {
        super();
        this.status = status;
        this.message = message;
        errors = Arrays.asList(error);
    }
}

示例响应

  {
      "status": "BAD_REQUEST",
      "message": "Validation failed for argument [0] in public void com.ns.hospitalmanagement.resource.PatientResource.createPatient(com.ns.hospitalmanagement.entity.Patient) with 2 errors: [Field error in object 'patient' on field 'name': rejected value [null]; codes [NotNull.patient.name,NotNull.name,NotNull.java.lang.String,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [patient.name,name]; arguments []; default message [name]]; default message [Name Can not be Null]] [Field error in object 'patient' on field 'name': rejected value [null]; codes [NotEmpty.patient.name,NotEmpty.name,NotEmpty.java.lang.String,NotEmpty]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [patient.name,name]; arguments []; default message [name]]; default message [Name Can not be Empty]] ",
      "errors": [
        "Name Can not be Null",
        "Name Can not be Empty"
      ]
    }

17

尝试覆盖方法handleMethodArgumentNotValid,在我的情况下,这个方法变成了这样:

  @Override
  protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
                                                                HttpHeaders headers, HttpStatus status,
                                                                WebRequest request) {
    String errorMessage = ex.getBindingResult().getFieldErrors().stream()
                 .map(DefaultMessageSourceResolvable::getDefaultMessage)
                 .findFirst()
                 .orElse(ex.getMessage());
    return response(ex, request, HttpStatus.BAD_REQUEST, errorMessage);
  }

  private ResponseEntity<Object> response(Exception ex, WebRequest request, HttpStatus status,
                                          String message) {
    return handleExceptionInternal(ex, message, header(), status, request);
  }

1
我在Spring 3.1.0中遇到了这个异常。它意味着你需要使用@Overridemethod handleMethodArgumentNotValid来处理方法参数无效,而不是使用@ExceptionHandler(MethodArgumentNotValidException.class)。
我的代码:
@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler{


  @Override
  protected ResponseEntity<Object> handleMethodArgumentNotValid(
      MethodArgumentNotValidException ex,
      HttpHeaders headers,
      HttpStatusCode status,
      WebRequest request) {
    var apiError = new ApiError(HttpStatus.resolve(status.value()), ex.getMessage());
    return new ResponseEntity<>(apiError, status);
  }

}

0

对于任何使用Spring WebFlux尝试捕获和映射引发的验证错误的人,您需要使用WebExchangeBindException而不是MethodArgumentNotValidException。

可以在此处找到示例: {{link1:Spring Reactive - Exception Handling for Method Not Allowed Exception not triggering post Spring 3.0.0 & Java 17 upgrade}}


0
如果有人仍然需要帮助,你可以直接覆盖这个方法。 示例代码返回一个问题细节,并使细节可读。
 /**
   * Catches Jakarta validation exceptions, returns Problem Detail with appropriate message.
   * We're overriding Spring handler to provide better message detail (Standard is: Invalid request content.)
   * @param MethodArgumentNotValidException (Jakarta Validation exception)
   * @return Problem detail, with the list of the error messages represented as readable plain text (as according to ProblemDetail rfc)
   */
@Override
  protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
    List<String> errorMessages = ex.getBindingResult().getFieldErrors().stream()
            .map(FieldError::getDefaultMessage)
            .collect(Collectors.toList());
    String plainMsg = String.join(",",errorMessages);
    return new ResponseEntity<> (problemDetailForStatusAndDetail(HttpStatus.BAD_REQUEST, plainMsg),HttpStatus.BAD_REQUEST);
  }

0
你的问题是从ResponseEntityExceptionHandler类扩展而来的。 你仍然可以将该处理程序用于其他异常类型,但对于处理MethodArgumentNotValid,必须使用自定义类&方法。
有一个自定义的ResponseError对象类,包含errorCode和errorMessage。
@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
public class ValidationErrorHandler {
   
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public final ResponseEntity<ResponseError> handleMethodArgumentNotValidException(
            MethodArgumentNotValidException ex) {
        HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
        ResponseError response = ResponseError.builder().errorCode(ErrorCode.INVALID_ARGUMENTS.getCode())
                .errorMessage(getMessages(ex.getBindingResult())).build();
        return new ResponseEntity<>(response, httpStatus);
    }

    private String getMessages(BindingResult bindingResult) {
        return Optional.ofNullable(bindingResult.getFieldError()).map(DefaultMessageSourceResolvable::getDefaultMessage)
                .orElse("");
    }
}

0

类 ResponseEntityExceptionHandler 是一个抽象类,其中有一个被 @ExceptionHandler 注解标记的 final 方法,其签名为

@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) throws Exception {

如果在您的应用程序中有一个处理上述任何异常的方法,则需要覆盖ResponseEntityExceptionHandler提供的方法。假设您想在应用程序中处理MethodArgumentNotValidException,则您的Handler类代码应如下所示:

@RestControllerAdvice
public final class WebRequestExceptionHandler extends ResponseEntityExceptionHandler{
    
@Override
    @ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR, value = HttpStatus.INTERNAL_SERVER_ERROR)
    protected ResponseEntity<Object> handleMethodArgumentNotValid(
            MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {

         List<String> errorList = ex
                    .getBindingResult()
                    .getFieldErrors()
                    .stream()
                    .map(fieldError -> fieldError.getDefaultMessage())
                    .collect(Collectors.toList());
            ErrorDetails errorDetails = new ErrorDetails(HttpStatus.BAD_REQUEST, ex.getLocalizedMessage(), errorList);
            
        return handleExceptionInternal(ex, errorDetails, headers, status, request);
    }
}

请注意,在我的 WebRequestExceptionHandler 类中,我没有使用 @ExceptionHandler(MethodArgumentNotValidException.class) 注释该方法,因为这会导致模糊映射异常。
@Data
@NoArgsConstructor
public class ErrorDetails {
     private HttpStatus status;
        private String message;
        private List<String> errors;

        public ErrorDetails(HttpStatus status, String message, List<String> errors) {
            super();
            this.status = status;
            this.message = message;
            this.errors = errors;
        }
}



网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接