java BindingResult方法参数的存在是否确定引发异常?

6xfqseft  于 2023-03-06  发布在  Java
关注(0)|答案(3)|浏览(149)

我有一个Spring @RestController,它有一个POST端点,定义如下:

@RestController
@Validated
@RequestMapping("/example")
public class Controller {

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public ResponseEntity<?> create(@Valid @RequestBody Request request,
                                    BindingResult _unused, // DO NOT DELETE
                                    UriComponentsBuilder uriBuilder) {
        // ...
    }
}

它还有一个针对javax.validation.ConstraintViolationException的异常处理程序:

@ExceptionHandler({ConstraintViolationException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
ProblemDetails handleValidationError(ConstraintViolationException e) {...}

我们的Spring-Boot应用使用spring-boot-starter-validation进行验证,Request对象使用javax.validation.*注解对各个字段应用约束,如下所示:

public class Request {

    private Long id;

    @Size(max = 64, message = "name length cannot exceed 64 characters")
    private String name;

    // ...
}

如上所述,如果你POST了一个无效的请求,验证会抛出一个ConstraintViolationException,这个异常会被异常处理器处理,我们有单元测试,一切正常。
我注意到post方法中没有使用BindingResult(名称_unused和注解//DO NOT DELETE是一种危险信号)我继续删除参数,突然,我的测试中断了--入站请求仍然有效,但是它不再抛出ConstraintValidationException ...现在它抛出MethodArgumentNotValidException!不幸的是,我不能使用这个异常,因为它不"t以我需要的格式包含失败的验证(也不包含我需要的所有数据)。
为什么参数列表中的BindingResult控制抛出哪个异常?当javax.validation确定请求主体无效时,我如何删除未使用的变量并仍然抛出ConstraintViolationException
Spring Boot2.5.5

  • Spring启动器腹板
  • Spring启动起动器验证

17.中国梦

lmyy7pcs

lmyy7pcs1#

此处涉及两个层次的确认,按以下顺序进行:

*控制器层

  • 当控制器方法的参数用@RequestBody@ModelAttribute@Valid@Validated或任何名称以“Valid”开头的注解注解时启用(逻辑参考此)。
  • 基于DataBinder的东西
  • 只能验证请求
  • 如果出现验证错误,并且控制器方法中没有BindingResult参数,则抛出org.springframework.web.bind.MethodArgumentNotValidException,否则继续调用控制器方法,并使用BindingResult参数捕获验证错误信息。
    *Bean的方法层
  • 如果springbean用@Validated注解,并且方法参数或返回值仅用bean验证注解(如@Valid@Size等)注解,则为springbean启用。
  • 基于AOP技术,方法拦截器是MethodValidationInterceptor
  • 可以验证请求和响应
  • 如果出现验证错误,则抛出javax.validation.ConstraintViolationException

最后两层中的验证将委托bean验证来执行实际的验证。
因为控制器实际上是一个springbean,所以当调用控制器方法时,两层中的验证都可以生效,这在您的案例中得到了确切的演示,其中会发生以下情况:

  1. DataBinder确认请求不正确,但由于控制器方法具有BindingResult参数,因此它跳过抛出MethodArgumentNotValidException并继续调用控制器方法
  2. MethodValidationInterceptor验证请求不正确,并抛出ConstraintViolationException
    文档没有明确提到这种行为。我在阅读源代码后做了以上总结。我同意这是令人困惑的,特别是在您的情况下,当验证在两个层中启用,也与BindingResult参数。您可以看到,bean验证实际上验证了两次请求,这听起来很尴尬...
    因此,要解决您的问题,您可以禁用控制器层的DataBinder中的验证,并始终依赖于bean方法级验证。
    要对所有控制器全局禁用它,您可以使用以下@InitBinder方法创建@ControllerAdvice
@ControllerAdvice
public class InitBinderControllerAdvice {

    @InitBinder
    private void initBinder(WebDataBinder binder) {
        binder.setValidator(null);
    }
}

要仅对一个控制器禁用该方法,可以将此@InitBinder方法添加到该控制器:

@RestController
@Validated
@RequestMapping("/example")
public class Controller {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.setValidator(null);
   }

}

然后,即使从控制器方法中删除BindingResult,它也应该抛出ConstraintViolationException

lpwwtiir

lpwwtiir2#

我不知道控制器方法中BindingResult的存在可以修改抛出异常的类型,因为我以前从未将其作为参数添加到控制器方法中。我通常看到的是,请求主体验证失败时抛出MethodArgumentNotValidException,请求参数时抛出ConstraintViolationException。路径变量和头值冲突。MethodArgumentNotValidException中的错误详细信息格式可能与ConstraintViolationException中的格式不同,但是它通常包含了你所需要的关于错误的所有信息。下面是我为你的控制器写的一个异常处理类:

package com.example.demo;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class ControllerExceptionHandler {
    public static final Logger LOGGER = LoggerFactory.getLogger(ControllerExceptionHandler.class);

    @ExceptionHandler({ ConstraintViolationException.class })
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Map<String, Object> handleValidationError(ConstraintViolationException exception) {
        LOGGER.warn("ConstraintViolationException thrown", exception);
        Map<String, Object> response = new HashMap<>();
        List<Map<String, String>> errors = new ArrayList<>();

        for (ConstraintViolation<?> violation : exception.getConstraintViolations()) {
            Map<String, String> transformedError = new HashMap<>();
            
            String fieldName = violation.getPropertyPath().toString();
            transformedError.put("field", fieldName.substring(fieldName.lastIndexOf('.') + 1));
            transformedError.put("error", violation.getMessage());

            errors.add(transformedError);
        }
        response.put("errors", errors);

        return response;
    }

    @ExceptionHandler({ MethodArgumentNotValidException.class })
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Map<String, Object> handleValidationError(MethodArgumentNotValidException exception) {
        LOGGER.warn("MethodArgumentNotValidException thrown", exception);
        Map<String, Object> response = new HashMap<>();

        if (exception.hasFieldErrors()) {
            List<Map<String, String>> errors = new ArrayList<>();

            for (FieldError error : exception.getFieldErrors()) {
                Map<String, String> transformedError = new HashMap<>();
                transformedError.put("field", error.getField());
                transformedError.put("error", error.getDefaultMessage());

                errors.add(transformedError);
            }
            response.put("errors", errors);
        }

        return response;
    }
}

它将MethodArgumentNotValidExceptionConstraintViolationException转换为以下相同的错误响应JSON:

{
    "errors": [
        {
            "field": "name",
            "error": "name length cannot exceed 64 characters"
        }
    ]
}

ConstraintViolationException相比,MethodArgumentNotValidException缺少哪些信息?

kpbpu008

kpbpu0083#

来自质量标准:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/MethodArgumentNotValidException.html

@Valid @RequestBody Request request

如果你的对象是无效的,我们总是得到一个MethodArgumentNotValidException。这里的区别取决于BindingResult ...
如果没有BindingResult,则会按预期引发MethodArgumentNotValidException。
使用BindingResult,错误将被插入到BindingResult中。我们经常要检查bindresult是否有错误,并对其进行处理。

if (bindingResult.hasErrors()) {  
    // handle error or create bad request status
}

绑定结果:* “表示绑定结果的常规接口。扩展接口以实现错误注册功能,允许应用验证程序,并添加特定于绑定的分析和模型构建。“*
您可以再次检查绑定结果中的错误。我没有看到完整的代码,所以我不知道是什么原因导致ConstraintViolationException,但我猜您跳过了绑定结果中的错误,继续将实体插入数据库,违反了一些约束。

相关问题