SpringBoot不处理javax.validation.ConstraintViolationException

bos*_*ava 14 java jpa-2.0 spring-boot

我已经在我的Entity类中定义了用于验证电子邮件的模式.在我的验证异常处理程序类中,我为ConstraintViolationException添加了处理程序.我的应用程序使用SpringBoot 1.4.5.

Profile.java

@Entity
@EntityListeners(AuditingEntityListener.class)
@Table(name = "profile")
public class Profile extends AuditableEntity {

  private static final long serialVersionUID = 8744243251433626827L;

  @Column(name = "email", nullable = true, length = 250)
  @NotNull
  @Pattern(regexp = "^([^ @])+@([^ \\.@]+\\.)+([^ \\.@])+$")
  @Size(max = 250)
  private String email;
....
}
Run Code Online (Sandbox Code Playgroud)

ValidationExceptionHandler.java

@ControllerAdvice
public class ValidationExceptionHandler extends ResponseEntityExceptionHandler {

  private MessageSource messageSource;

  @Autowired
  public ValidationExceptionHandler(MessageSource messageSource) {
    this.messageSource = messageSource;
  }

  @ExceptionHandler(ConstraintViolationException.class)
  public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex,
  WebRequest request) {
    List<String> errors = new ArrayList<String>();
    ....
    }
} 
Run Code Online (Sandbox Code Playgroud)

当我运行我的代码并传递无效的电子邮件地址时,我得到以下异常.handleConstraintViolation中的代码永远不会执行.在异常中返回的http状态是500,但我想返回400.任何想法我怎么能实现这一点?

2017-07-12 22:15:07.078 ERROR 55627 --- [nio-9000-exec-2] o.h.c.s.u.c.UserProfileController        : Validation failed for classes [org.xxxx.common.service.user.domain.Profile] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must match "^([^ @])+@([^ \.@]+\.)+([^ \.@])+$"', propertyPath=email, rootBeanClass=class org.xxxx.common.service.user.domain.Profile, messageTemplate='{javax.validation.constraints.Pattern.message}'}]

javax.validation.ConstraintViolationException: Validation failed for classes [org.xxxx.common.service.user.domain.Profile] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
ConstraintViolationImpl{interpolatedMessage='must match "^([^ @])+@([^ \.@]+\.)+([^ \.@])+$"', propertyPath=email, rootBeanClass=class org.xxxx.common.service.user.domain.Profile, messageTemplate='{javax.validation.constraints.Pattern.message}'}]

at  org.hibernate.cfg.beanvalidation.BeanValidationEventListener.validate(BeanValidationEventListener.java:138)

at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.onPreInsert(BeanValidationEventListener.java:78)    
Run Code Online (Sandbox Code Playgroud)

nim*_*mai 18

你无法捕捉,ConstraintViolationException.class因为它没有传播到你的代码层,它被较低层捕获,包裹并在另一种类型下重新抛出.因此,触及您的Web图层的异常不是ConstraintViolationException.

就我而言,它是一个TransactionSystemException.我正在使用@TransactionalSpring中的注释JpaTransactionManager.EntityManager中抛出一个异常回滚出头时出现错误的交易,将其转化为一个TransactionSystemException由JpaTransactionManager.

所以你可以这样做:

@ExceptionHandler({ TransactionSystemException.class })
public ResponseEntity<RestResponseErrorMessage> handleConstraintViolation(Exception ex, WebRequest request) {
    Throwable cause = ((TransactionSystemException) ex).getRootCause();
    if (cause instanceof ConstraintViolationException) {
        Set<ConstraintViolation<?>> constraintViolations = ((ConstraintViolationException) cause).getConstraintViolations();
        // do something here
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 介意评论为什么答案被否决了吗?我知道在您的特定版本的框架(spring boot/hibernate/...)的情况下,确切的例外情况可能会有所不同,但原则是相同的,应该适用于提出问题的人。 (3认同)
  • 这是迄今为止最好的答案。我注意到他们在 Spring 版本 &gt;= 2.4.0 中修复了这个问题,这意味着您现在可以执行以下操作:`@ExceptionHandler(ConstraintViolationException.class)` 并且您现在也可以处理所有其他嵌套异常。 (2认同)

小智 6

以下解决方案基于 Spring Boot 2.1.2。

为了澄清事情......正如nimai已经正确提到的那样:

您无法捕获 ConstraintViolationException.class,因为它不会传播到代码的该层,而是被较低层捕获,包装并在另一种类型下重新抛出。因此,访问 Web 层的异常不是 ConstraintViolationException。

在您的情况下,它可能是 a DataIntegrityViolationException,它指出了持久层中的问题。但你不想让它发展到那么远。


解决方案

正如Ena提到的,使用@Valid作为方法参数给出的实体的注释。在我的版本中,它缺少注释(如果没有注释,则无法正确解析到您的实体中,并且属性会产生值,例如。):org.springframework.web.bind.annotation.RequestBody@RequestBodyProfileDtoProfileDtonullNullPointerException

@RequestMapping(value = "/profile", method = RequestMethod.POST)
public ProfileDto createProfile(@Valid @RequestBody ProfileDto profile){
    ...
}
Run Code Online (Sandbox Code Playgroud)

org.springframework.web.bind.MethodArgumentNotValidException然后,这将返回您想要的状态代码 400 和一些默认响应正文,甚至在到达持久层之前还附有。的处理MethodArgumentNotValidException在 中定义org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler。

这是另一个主题,但您可以选择通过创建@ControllerAdvicewith@ExceptionHandler(MethodArgumentNotValidException.class)并根据您的需要自定义响应正文来覆盖该行为,因为默认错误响应正文不是最佳的,甚至在排除 ErrorMvcAutoConfiguration 时不存在。

注意:找到将结果扩展为 的@ExceptionHandler(MethodArgumentNotValidException.class)内部,因为其中已经有为 定义的异常处理程序。因此,只需将其放入另一个类中即可,无需扩展任何内容。@ControllerAdviceResponseEntityExceptionHandlerIllegalStateExceptionResponseEntityExceptionHandlerMethodArgumentNotValidException@ControllerAdvice


替代手动方法

我看到您还可以手动触发电子邮件模式的验证(请参阅手动调用 Spring Annotation Validation)。我自己没有测试过,但我个人不喜欢这种方法,因为它只会使控制器代码变得臃肿,而且我目前无法想到需要它的用例。

我希望能够帮助遇到类似问题的其他人。


Ena*_*Ena 5

只是想补充一点。我试图做同样的事情,验证实体。然后我意识到如果您验证控制器的输入,Spring 已经具备了开箱即用的所有功能。

@RequestMapping(value = "/profile", method = RequestMethod.POST)
public ProfileDto createProfile(@Valid ProfileDto profile){
...    
}
Run Code Online (Sandbox Code Playgroud)

该@Valid批注将使用 javax.validation 批注触发验证。

假设您的个人资料用户名上有一个 Pattern 注释,正则表达式不允许空格。

Spring 将构建一个状态为 400(错误请求)的响应和一个像这样的主体:

{
    "timestamp": 1544453370570,
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            "codes": [
                "Pattern.ProfileDto.username",
                "Pattern.username",
                "Pattern.java.lang.String",
                "Pattern"
            ],
            "arguments": [
                {
                    "codes": [
                        "profileDto.username",
                        "username"
                    ],
                    "arguments": null,
                    "defaultMessage": "username",
                    "code": "username"
                },
                [],
                {
                    "defaultMessage": "^[A-Za-z0-9_\\-.]+$",
                    "arguments": null,
                    "codes": [
                        "^[A-Za-z0-9_\\-.]+$"
                    ]
                }
            ],
            "defaultMessage": "must match \"^[A-Za-z0-9_\\-.]+$\"",
            "objectName": "profileDto",
            "field": "username",
            "rejectedValue": "Wr Ong",
            "bindingFailure": false,
            "code": "Pattern"
        }
    ],
    "message": "Validation failed for object='profileDto'. Error count: 1",
    "path": "/profile"
}
Run Code Online (Sandbox Code Playgroud)


Pat*_*ick 0

我认为你应该添加@ResponseStatus(HttpStatus.BAD_REQUEST)到你的@ExceptionHandler:

@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException ex, WebRequest request) {
    List<String> errors = new ArrayList<String>();
    ....
}
Run Code Online (Sandbox Code Playgroud)