我已经在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;
....
}
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>();
....
}
}
当我运行代码并传递无效的电子邮件地址时,出现以下异常。 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)
您无法捕获ConstraintViolationException.class
,因为它没有传播到您的代码层,它被较低的层捕获,并包装并重新抛出另一种类型。因此,影响您的Web层的异常不是ConstraintViolationException
。
就我而言,这是TransactionSystemException
。我正在使用Spring和@Transactional
的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
}
}
只需添加一些内容。我试图做同样的事情,验证实体。然后我意识到,如果您验证控制器的输入,Spring便会提供所有可用的东西。
@RequestMapping(value = "/profile", method = RequestMethod.POST)
public ProfileDto createProfile(@Valid ProfileDto profile){
...
}
@Valid
批注将使用javax.validation批注触发验证。
假设您的个人资料用户名上具有模式注释,并且正则表达式不允许使用空格。
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"
}
您无法捕获ConstraintViolationException.class,因为它没有传播到代码的那层,它被较低的层捕获,并包装并重新抛出为另一种类型。因此,到达您的Web层的异常不是ConstraintViolationException。因此,您可以执行以下操作:
@ExceptionHandler({TransactionSystemException.class})
protected ResponseEntity<Object> handlePersistenceException(final Exception ex, final WebRequest request) {
logger.info(ex.getClass().getName());
//
Throwable cause = ((TransactionSystemException) ex).getRootCause();
if (cause instanceof ConstraintViolationException) {
ConstraintViolationException consEx= (ConstraintViolationException) cause;
final List<String> errors = new ArrayList<String>();
for (final ConstraintViolation<?> violation : consEx.getConstraintViolations()) {
errors.add(violation.getPropertyPath() + ": " + violation.getMessage());
}
final ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, consEx.getLocalizedMessage(), errors);
return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
}
final ApiError apiError = new ApiError(HttpStatus.INTERNAL_SERVER_ERROR, ex.getLocalizedMessage(), "error occurred");
return new ResponseEntity<Object>(apiError, new HttpHeaders(), apiError.getStatus());
}
以下解决方案基于Spring Boot 2.1.2。
为了澄清一下……因为nimai已经正确提及:
您无法捕获ConstraintViolationException.class,因为它没有传播到代码的这一层,它被较低的层捕获,并包装并重新抛出另一种类型。这样,到达您的Web层的异常不是ConstraintViolationException。
在您的情况下,可能是DataIntegrityViolationException
,它指出了持久层中的问题。但是您不想让它走那么远。
@Valid
注释用于作为方法参数指定的实体,如Ena所述。在我的版本中,它缺少org.springframework.web.bind.annotation.RequestBody
批注(如果没有@RequestBody
批注,则ProfileDto
无法正确解析到您的ProfileDto
实体中,并且这些属性会产生null
值,例如NullPointerException
。):
@RequestMapping(value = "/profile", method = RequestMethod.POST)
public ProfileDto createProfile(@Valid @RequestBody ProfileDto profile){
...
}
然后这将返回您想要的状态代码400和一些默认的响应正文,并在到达持久层之前将其附带org.springframework.web.bind.MethodArgumentNotValidException
。 MethodArgumentNotValidException
的处理在org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
中定义。
这是另一个主题,但是您可以选择通过使用@ControllerAdvice
创建@ExceptionHandler(MethodArgumentNotValidException.class)
来覆盖该行为,并根据需要自定义响应主体,因为默认错误响应主体不是最佳的,甚至在出现以下情况时也不存在排除ErrorMvcAutoConfiguration。
注意:将@ExceptionHandler(MethodArgumentNotValidException.class)
定位在扩展@ControllerAdvice
的ResponseEntityExceptionHandler
内将导致生成IllegalStateException
,因为在ResponseEntityExceptionHandler
中已经是为MethodArgumentNotValidException
定义的异常处理程序。因此,只需将其放入另一个@ControllerAdvice
类中,而无需扩展任何内容。
我看到您也可以手动触发电子邮件模式的验证(请参阅Manually call Spring Annotation Validation)。我没有亲自测试它,但是我个人不喜欢这种方法,因为它只是膨胀了您的控制器代码,我目前无法想到需要它的用例。
我希望能帮助其他人遇到类似的问题。
我认为您应该将@ResponseStatus(HttpStatus.BAD_REQUEST)
添加到@ExceptionHandler
:
我会仔细检查您已导入正确的ConstraintViolationException
只需检查所有例外,然后选择所需的例外
@ResponseBody
@ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
@ExceptionHandler(DataIntegrityViolationException.class)
public Map errorHandler(DataIntegrityViolationException ex) {
Map map = new HashMap();
map.put("rs_code", 422);
map.put("rs_msg", "data existed !");
return map;
}
您可以通过在@controllerAdvice中添加它来处理org.hibernate.exception.ConstraintViolationException。>
@ ExceptionHandler(DataIntegrityViolationException.class)公共ResponseEntity handleConstraintViolationException(Exception ex){