Spring Boot父项目(2.3.0.RELEASE)异常处理方法在属性为null时未被调用。

huangapple 未分类评论50阅读模式
英文:

Spring boot parent (2.3.0.RELEASE) exception handling method not called when attributes null

问题

我有一个Rest Controller,其中有一个requestObject,包含3个属性,它们是必需的。基本上,我有以下的类,其中包含处理@NotBlank异常的constraintViolationException方法。

@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
    // @Validate用于验证路径变量和请求参数
    @ExceptionHandler(ConstraintViolationException.class)
    public void constraintViolationException(HttpServletResponse response) throws IOException {
        //TODO - 提供自定义错误消息
        response.sendError(HttpStatus.BAD_REQUEST.value());
    }
}

我的Controller如下所示:

@RestController
public class MyRestController {
    @PostMapping(value = "/processMyPayment")
    public ResponseEntity<?> processMyPayment(@Valid MyRequest myRequest) {
        MyResponse myResponse = new MyResponse();
        return new ResponseEntity<>(myResponse, HttpStatus.OK);
    }
    // ...
}

当我添加MyExceptionHandler类并发送空属性到我的rest controller时,由于值为空,它应该调用constraintViolationException方法。但事实并非如此,我得到了以下结果:

然而,当我移除extends ResponseEntityExceptionHandler时,我会得到整个堆栈跟踪:

"timestamp": "2020-06-05T13:01:47.715+00:00",
"status": 400,
"error": "Bad Request",
"trace": "org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 3 errors\nField error in object 'myRequest' on field 'amount': rejected value [null]; codes [NotBlank.myRequest.amount,NotBlank.amount,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [myRequest.amount,amount]; arguments []; default message [amount]]; default message [Amount name mandatory]\nField error in object 'myRequest' on field 'accountId': rejected value [null]; codes [NotBlank.myRequest.accountId,NotBlank.accountId,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [myRequest.accountId,accountId]; arguments []; default message [accountId]]; default message [Account id is mandatory]\nField error in object 'myRequest' on field 'accountName': rejected value [null]; codes [NotBlank.myRequest.accountName,NotBlank.accountName,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [myRequest.accountName,accountName]; arguments []; default message [accountName]]; default message [Account name mandatory]...

我基本上想要实现的是,不提供堆栈跟踪,而是仅向空字段添加错误消息,指出这些字段不能为空。

有任何关于如何实现这一目标的想法吗?非常感谢您的帮助。

英文:

I have a rest controller that has a requestObject that contains 3 attributes and they are mandatory. Basically I have the class below which contains method constraintViolationException to deal with @NotBlank exception.

@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler{
    // @Validate For Validating Path Variables and Request Parameters
    @ExceptionHandler(ConstraintViolationException.class)
    public void constraintViolationException(HttpServletResponse response) throws IOException {
  //TODO - to provide custom error message
        response.sendError(HttpStatus.BAD_REQUEST.value());
    }

}

My Controller is shown below:

@RestController
public class MyRestController {



    @PostMapping(value = &quot;/processMyPayment&quot;)
    public  ResponseEntity&lt;?&gt; processMyPayment(@Valid MyRequest myRequest ) {

        MyResponse myResponse = new MyResponse();
        return new ResponseEntity&lt;&gt;(myTResponse, HttpStatus.OK);
    }
...

When I add the class MyExceptionHandler and send null attributes it my rest controller it should call the constraintViolationException since the values are null but it is not the case and I get the following result:
Spring Boot父项目(2.3.0.RELEASE)异常处理方法在属性为null时未被调用。

. However when I remove the extends ResponseEntityExceptionHandler I get the whole stacktrace:

&quot;timestamp&quot;: &quot;2020-06-05T13:01:47.715+00:00&quot;,
&quot;status&quot;: 400,
&quot;error&quot;: &quot;Bad Request&quot;,
&quot;trace&quot;: &quot;org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 3 errors\nField error in object &#39;myRequest&#39; on field &#39;amount&#39;: rejected value [null]; codes [NotBlank.myRequest.amount,NotBlank.amount,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [myRequest.amount,amount]; arguments []; default message [amount]]; default message [Amount name mandatory]\nField error in object &#39;myRequest&#39; on field &#39;accountId&#39;: rejected value [null]; codes [NotBlank.myRequest.accountId,NotBlank.accountId,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [myRequest.accountId,accountId]; arguments []; default message [accountId]]; default message [Account id is mandatory]\nField error in object &#39;myRequest&#39; on field &#39;accountName&#39;: rejected value [null]; codes [NotBlank.myRequest.accountName,NotBlank.accountName,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [myRequest.accountName,accountName]; arguments []; default message [accountName]]; default message [Account name mandatory]\r\n\tat org.springframework.web.method.annotation.ModelAttributeMethodProcessor.resolveArgument(ModelAttributeMethodProcessor.java:164)\r\n\tat org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:167)\r\n\tat org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:879)\r\n\tat org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:793)\r\n\tat org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040)\r\n\tat org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943)\r\n\tat org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)\r\n\tat org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:660)\r\n\tat org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)\r\n\tat javax.servlet.http.HttpServlet.service(HttpServlet.java:741)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)\r\n\tat org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)\r\n\tat org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)\r\n\tat org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)\r\n\tat org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)\r\n\tat org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)\r\n\tat org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)\r\n\tat org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)\r\n\tat org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)\r\n\tat org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)\r\n\tat org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:373)\r\n\tat org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)\r\n\tat org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)\r\n\tat org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1590)\r\n\tat org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)\r\n\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)\r\n\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)\r\n\tat org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)\r\n\tat java.base/java.lang.Thread.run(Thread.java:832)\r\n&quot;,
&quot;message&quot;: &quot;Validation failed for object=&#39;myRequest&#39;. Error count: 3&quot;,
&quot;errors&quot;:[
{&quot;codes&quot;:[&quot;NotBlank.myRequest.amount&quot;, &quot;NotBlank.amount&quot;, &quot;NotBlank.java.lang.String&quot;, &quot;NotBlank&quot; ],…},
{&quot;codes&quot;:[&quot;NotBlank.myRequest.accountId&quot;, &quot;NotBlank.accountId&quot;, &quot;NotBlank.java.lang.String&quot;, &quot;NotBlank&quot; ],…},
{&quot;codes&quot;:[&quot;NotBlank.myRequest.accountName&quot;, &quot;NotBlank.accountName&quot;, &quot;NotBlank.java.lang.String&quot;, &quot;NotBlank&quot; ],…}
],
&quot;path&quot;: &quot;/processMyPayment&quot;
}

Basically what I am trying to achieve is that instead of providing the stack-trace I just want to add the fields that are null with an error message that those fields cannot be null.

Any idea how to achieve this?
Thanks in advance.

答案1

得分: 0

请尝试以下代码:

/**
 * 处理 javax.validation.ConstraintViolationException。在 @Validated 失败时抛出。
 *
 * @param ex ConstraintViolationException 异常
 * @return 错误信息
 */
@ExceptionHandler(javax.validation.ConstraintViolationException.class)
public ResponseEntity<Object> handleConstraintViolation(
        javax.validation.ConstraintViolationException ex) {
    // 违规列表
    // ex.getConstraintViolations();
    
    // 返回自定义错误
    return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}

你应该覆盖 ResponseEntityExceptionHandlerhandleMethodArgumentNotValid() 方法,就像这样:

/**
 * 处理 @see MethodArgumentNotValidException。当对象验证失败时触发。
 *
 * @param ex      当验证失败时抛出的 @see MethodArgumentNotValidException
 * @param headers	HttpHeaders @see HttpHeaders
 * @param status	HttpStatus  @see HttpStatus
 * @param request	WebRequest @see WebRequest
 * @return ResponseEntity<Object>
 */
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
        MethodArgumentNotValidException ex,
        HttpHeaders headers,
        HttpStatus status,
        WebRequest request) {
        
        // 获取 Binding Result
        Object bindingResult = ex.getBindingResult();
        // 获取特定字段错误
        Object fieldErrors = ex.getBindingResult().getFieldErrors();
        // 获取错误计数
        int errorCount = ex.getBindingResult().getErrorCount();
            
        // 返回,你也可以基于 fieldErrors 和 errorCount 创建自定义响应,然后将其放入下面的 body 中。
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(fieldErrors);
}

当你按照上述方式覆盖了这个函数后,你会发现无论何时任何 @Valid 验证失败时,此方法都会被调用。你可以更改返回类型。

在测试完成后告诉我。你还可以查看一个经过测试的开源项目,用于自定义异常处理程序和响应 ->
https://github.com/AccessGateLabs/response-builder/

处理自定义异常的特定类的链接 -> https://github.com/AccessGateLabs/response-builder/blob/master/src/main/java/com/accessgatelabs/oss/builder/exceptions/RestExceptionHandler.java


<details>
<summary>英文:</summary>

Try this:

/**
* Handles javax.validation.ConstraintViolationException. Thrown when @Validated fails.
*
* @param ex the ConstraintViolationException
* @return error
*/
@ExceptionHandler(javax.validation.ConstraintViolationException.class)
public ResponseEntity<Object> handleConstraintViolation(
javax.validation.ConstraintViolationException ex) {
// list of violations
// ex.getConstraintViolations();

    // return custom error
    return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}
You should override the method ```handleMethodArgumentNotValid()``` of ```ResponseEntityExceptionHandler```, like this:

/**
* Handle @see MethodArgumentNotValidException. Triggered when an object fails validation.
*
* @param ex the @see MethodArgumentNotValidException that is thrown when validation fails
* @param headers HttpHeaders @see HttpHeaders
* @param status HttpStatus @see HttpStatus
* @param request WebRequest @see WebRequest
* @return ResponseEntity<Object>
*/
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status,
WebRequest request) {

        // get Binding Result
        Object bindingResult = ex.getBindingResult();
        // get specific field errors
        Object fieldErrors = ex.getBindingResult().getFieldErrors();
        // get error counts
        int errorCount = ex.getBindingResult().getErrorCount();
           	    
        // return, you can create custom responses based on fieldErrors and errorCounts as well and send that in the body below.
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(fieldErrors);
}

When you override this function as above, you will find that this method gets called whenever any ```@Valid``` validation fails. You can change return type.

Let me know after you test it. You may also check an open source tested project for custom exception handler and responses -&gt;
[https://github.com/AccessGateLabs/response-builder/][1]

Link for specific class that handles custom exceptions -&gt; [https://github.com/AccessGateLabs/response-builder/blob/master/src/main/java/com/accessgatelabs/oss/builder/exceptions/RestExceptionHandler.java][2]


  [1]: https://github.com/AccessGateLabs/response-builder/
  [2]: https://github.com/AccessGateLabs/response-builder/blob/master/src/main/java/com/accessgatelabs/oss/builder/exceptions/RestExceptionHandler.java

</details>



# 答案2
**得分**: 0

你正在处理错误的异常。`ConstraintViolationException` 用于实体/领域对象验证。如果在控制器的方法参数上使用 `@Valid`,Spring 会在Web层引发 `MethodArgumentNotValidException`。您可以修改您的异常处理程序如下:

```java
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<CustomErrorResponse> handle(MethodArgumentNotValidException e) {
    // 您可以从异常中提取默认的错误消息
    val errors = getErrors(e);
    return ResponseEntity
            .status(HttpStatus.BAD_REQUEST)
            .contentType(MediaType.APPLICATION_JSON)
            .body(CustomErrorResponse.of(errors));
}
英文:

You are handling the wrong exception. ConstraintViolationException is meant for entity/domain object validation. Spring throws a MethodArgumentNotValidException in the web layer if using @Valid on controller's method argument. You could modify your exception handler as such:

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity&lt;CustomErrorResponse&gt; handle(MethodArgumentNotValidException e) {
    // You could extract default error messages from the exception
    val errors = getErrors(e)
    return ResponseEntity
            .status(HttpStatus.BAD_REQUEST)
            .contentType(MediaType.APPLICATION_JSON)
            .body(CustomErrorResponse.of(errors)));
}

答案3

得分: 0

最终我找到了问题所在,以防其他人遇到类似情况,我在这里发布出来。基本上,我必须覆盖以下方法:

@Override
protected ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
    //在这里添加自定义实现
    return this.handleExceptionInternal(ex, (Object)null, headers, status, request);
}
英文:

Finally i found out the issue in case of someone else come across this, i am posting it.Basically I had to override the following method:

    @Override
    protected ResponseEntity&lt;Object&gt; handleBindException(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
//add custom implementation here
            return this.handleExceptionInternal(ex, (Object)null, headers, status, request);
    }

huangapple
  • 本文由 发表于 2020年6月5日 21:26:36
  • 转载请务必保留本文链接:https://java.coder-hub.com/62216419.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定