如何从多层包装的ConstraintViolationException中提取约束违例列表的异常信息
解决ConstraintViolationException多层包装下提取错误信息的问题
看起来你已经离解决问题很近了,只是在异常拆解和ExceptionHandler的使用上卡壳了。我来一步步帮你搞定这个问题:
第一步:正确提取被包装的ConstraintViolationException
Spring的TransactionSystemException通常会把底层的校验异常特殊包装起来,直接用NestedExceptionUtils.getRootCause(e)有时候会因为包装逻辑的特殊性拿不到目标异常。你可以试试递归遍历异常链,针对性处理TransactionSystemException的情况:
private ConstraintViolationException extractConstraintViolation(Exception ex) { // 直接命中目标异常 if (ex instanceof ConstraintViolationException) { return (ConstraintViolationException) ex; } // 处理TransactionSystemException的特殊包装 if (ex instanceof TransactionSystemException) { Throwable nestedEx = ((TransactionSystemException) ex).getOriginalException(); if (nestedEx instanceof ConstraintViolationException) { return (ConstraintViolationException) nestedEx; } // 嵌套层还有异常,继续递归 if (nestedEx instanceof Exception) { return extractConstraintViolation((Exception) nestedEx); } } // 通用递归查找异常链 if (ex.getCause() != null && ex.getCause() instanceof Exception) { return extractConstraintViolation((Exception) ex.getCause()); } return null; }
第二步:用@ExceptionHandler全局捕获并返回错误信息
你需要创建一个全局异常处理器类,通过@RestControllerAdvice(REST接口场景)或@ControllerAdvice(普通MVC场景)来统一捕获异常,提取出你需要的interpolatedMessage返回给前端:
import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.transaction.TransactionSystemException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import java.util.stream.Collectors; @RestControllerAdvice public class GlobalExceptionHandler { // 处理被TransactionSystemException包装的校验异常 @ExceptionHandler(TransactionSystemException.class) public ResponseEntity<String> handleTransactionSystemException(TransactionSystemException ex) { ConstraintViolationException constraintEx = extractConstraintViolation(ex); if (constraintEx != null) { // 提取所有校验错误信息,单条的话直接取第一个即可 String errorMessage = constraintEx.getConstraintViolations() .stream() .map(ConstraintViolation::getInterpolatedMessage) .collect(Collectors.joining("; ")); return new ResponseEntity<>(errorMessage, HttpStatus.BAD_REQUEST); } // 非校验异常返回默认错误 return new ResponseEntity<>("Server error", HttpStatus.INTERNAL_SERVER_ERROR); } // 直接捕获未被包装的ConstraintViolationException @ExceptionHandler(ConstraintViolationException.class) public ResponseEntity<String> handleConstraintViolationException(ConstraintViolationException ex) { String errorMessage = ex.getConstraintViolations() .stream() .map(ConstraintViolation::getInterpolatedMessage) .collect(Collectors.joining("; ")); return new ResponseEntity<>(errorMessage, HttpStatus.BAD_REQUEST); } // 放入刚才的异常提取方法 private ConstraintViolationException extractConstraintViolation(Exception ex) { if (ex instanceof ConstraintViolationException) { return (ConstraintViolationException) ex; } if (ex instanceof TransactionSystemException) { Throwable nestedEx = ((TransactionSystemException) ex).getOriginalException(); if (nestedEx instanceof ConstraintViolationException) { return (ConstraintViolationException) nestedEx; } if (nestedEx instanceof Exception) { return extractConstraintViolation((Exception) nestedEx); } } if (ex.getCause() != null && ex.getCause() instanceof Exception) { return extractConstraintViolation((Exception) ex.getCause()); } return null; } }
关键注意点
- 之前用
NestedExceptionUtils.getRootCause没效果的核心原因:TransactionSystemException的原始异常是存在getOriginalException()方法里的,并非直接挂在标准的异常cause链最底层,所以需要针对性处理。 - 如果是普通Spring MVC项目而非REST接口,把
@RestControllerAdvice换成@ControllerAdvice,并给方法加上@ResponseBody注解即可。 - 若你的场景只会出现单条校验错误,也可以简化提取逻辑:
constraintEx.getConstraintViolations().iterator().next().getInterpolatedMessage()
这样处理后,前端就能准确收到你自定义校验器中设置的Customer must be 18 years old to become a member这条错误提示了。
内容的提问来源于stack exchange,提问作者Soccan
相关产品推荐
相关产品推荐

