我有一个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.中国梦
3条答案
按热度按时间lmyy7pcs1#
此处涉及两个层次的确认,按以下顺序进行:
*控制器层:
@RequestBody
或@ModelAttribute
、@Valid
或@Validated
或任何名称以“Valid”开头的注解注解时启用(逻辑参考此)。DataBinder
的东西BindingResult
参数,则抛出org.springframework.web.bind.MethodArgumentNotValidException
,否则继续调用控制器方法,并使用BindingResult
参数捕获验证错误信息。*Bean的方法层:
@Validated
注解,并且方法参数或返回值仅用bean验证注解(如@Valid
、@Size
等)注解,则为springbean启用。MethodValidationInterceptor
javax.validation.ConstraintViolationException
。最后两层中的验证将委托bean验证来执行实际的验证。
因为控制器实际上是一个springbean,所以当调用控制器方法时,两层中的验证都可以生效,这在您的案例中得到了确切的演示,其中会发生以下情况:
DataBinder
确认请求不正确,但由于控制器方法具有BindingResult
参数,因此它跳过抛出MethodArgumentNotValidException
并继续调用控制器方法MethodValidationInterceptor
验证请求不正确,并抛出ConstraintViolationException
文档没有明确提到这种行为。我在阅读源代码后做了以上总结。我同意这是令人困惑的,特别是在您的情况下,当验证在两个层中启用,也与
BindingResult
参数。您可以看到,bean验证实际上验证了两次请求,这听起来很尴尬...因此,要解决您的问题,您可以禁用控制器层的
DataBinder
中的验证,并始终依赖于bean方法级验证。要对所有控制器全局禁用它,您可以使用以下
@InitBinder
方法创建@ControllerAdvice
:要仅对一个控制器禁用该方法,可以将此
@InitBinder
方法添加到该控制器:然后,即使从控制器方法中删除
BindingResult
,它也应该抛出ConstraintViolationException
。lpwwtiir2#
我不知道控制器方法中
BindingResult
的存在可以修改抛出异常的类型,因为我以前从未将其作为参数添加到控制器方法中。我通常看到的是,请求主体验证失败时抛出MethodArgumentNotValidException
,请求参数时抛出ConstraintViolationException
。路径变量和头值冲突。MethodArgumentNotValidException
中的错误详细信息格式可能与ConstraintViolationException
中的格式不同,但是它通常包含了你所需要的关于错误的所有信息。下面是我为你的控制器写的一个异常处理类:它将
MethodArgumentNotValidException
和ConstraintViolationException
转换为以下相同的错误响应JSON:与
ConstraintViolationException
相比,MethodArgumentNotValidException
缺少哪些信息?kpbpu0083#
来自质量标准:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/MethodArgumentNotValidException.html
如果你的对象是无效的,我们总是得到一个MethodArgumentNotValidException。这里的区别取决于BindingResult ...
如果没有BindingResult,则会按预期引发MethodArgumentNotValidException。
使用BindingResult,错误将被插入到BindingResult中。我们经常要检查bindresult是否有错误,并对其进行处理。
绑定结果:* “表示绑定结果的常规接口。扩展接口以实现错误注册功能,允许应用验证程序,并添加特定于绑定的分析和模型构建。“*
您可以再次检查绑定结果中的错误。我没有看到完整的代码,所以我不知道是什么原因导致ConstraintViolationException,但我猜您跳过了绑定结果中的错误,继续将实体插入数据库,违反了一些约束。