此文章已同步更新至我的个人博客https://simonting.gitee.io
SpringBoot项目中一般使用hibernate-validator来对请求参数进行校验,但hibernate-validator提供的注解有限,有时候需要根据具体业务扩展自定义注解对参数进行校验。
前言:
在实际运用中,hibernate-validator的注解分为校验请求参数及请求体,两个校验的情况在处理上有一些差别,下面分开讲解。
校验请求参数
自定义注解
模拟对Id进行校验
@Documented
@Constraint(validatedBy = IdCheck.IdValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR,
ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface IdCheck {
String message() default "Id is invalid.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
class IdValidator implements ConstraintValidator<IdCheck, String> {
@Override
public void initialize(IdCheck constraintAnnotation) {
}
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
// 这里写业务校验逻辑,此处为模拟,id不允许等于001
return !Objects.equals(s, "001");
}
}
}
其中:
@Constraint(validatedBy = IdCheck.IdValidator.class) 指定具体校验逻辑实现类。
@Target 指定注解所修饰的对象范围
String message() default “Id is invalid.”; 可以自定义校验失败的提示内容
异常统一处理
当hebernate-validator校验的是请求参数时,校验失败抛出的异常类型为ConstraintViolationException。
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResult handleConstraintViolationException(ConstraintViolationException ex) {
Set<ConstraintViolation<?>> errs = ex.getConstraintViolations();
Set<String> fields = new HashSet<>();
Set<String> msgs = new HashSet<>();
for (ConstraintViolation<?> err : errs) {
String path = err.getPropertyPath().toString();
String field = StringUtils.substringAfterLast(path, ".");
if (StringUtils.isEmpty(field)) {
field = path;
}
fields.add(field);
String msg = err.getMessage();
msgs.add(msg);
}
String params = StringUtils.join(fields.toArray(), ",");
String cause = StringUtils.join(msgs.toArray(), ",");
Map<String, Object> errorParams = new HashMap<>();
errorParams.put("param", params);
errorParams.put("cause", cause);
return ErrorResult.builder()
.errorCode(500)
.errorMsg("参数校验失败")
.errorParams(errorParams)
.build();
}
}
测试
需要特别注意的是,对@PathVariable、@RequestParam修饰的请求参数校验时,需要在类上面加上@Validated注解
校验哪个参数就将注解放在前面即可
1、校验@PathVariable参数
@Slf4j
@Validated //校验@PathVariable、@RequestParam参数时需加上此注解
@RestController
@RequestMapping("/v1")
public class TestController {
@GetMapping("/check/{id}")
public ResponseEntity<?> check(@PathVariable @IdCheck String id) {
log.info("id is {}", id);
return new ResponseEntity<>(HttpStatus.OK);
}
}
使用postman发起请求测试:
2、校验@RequestParam参数
新增一个对name参数的校验自定义注解,作用在@RequestParam后
3、当@PathVariable参数与@RequestParam参数同时校验失败时
校验请求体
自定义注解
这里直接使用上面自定义的两个注解IdCheck、NameCehck。
异常统一处理
hibernate-validator在校验请求体时失败抛出的异常与校验请求参数时抛出的异常有差别,类型为MethodArgumentNotValidException,因此要对此类型的异常额外做全局异常统一处理。
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResult handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
List<FieldError> errs = ex.getBindingResult().getFieldErrors();
Set<String> fields = new HashSet<>();
Set<String> msgs = new HashSet<>();
for (FieldError err : errs) {
fields.add(err.getField());
String msg = err.getDefaultMessage();
msgs.add(msg);
}
String params = StringUtils.join(fields.toArray(), ",");
String cause = StringUtils.join(msgs.toArray(), ",");
Map<String, Object> errorParams = new HashMap<>();
errorParams.put("param", params);
errorParams.put("cause", cause);
return ErrorResult.builder()
.errorCode(500)
.errorMsg("参数校验失败")
.errorParams(errorParams)
.build();
}
测试
在需要校验的实体类前加上@Validated注解,在实体类内部各字段前面加上具体的校验注解。
@PostMapping("/checkUser")
public ResponseEntity<?> checkUser(@RequestBody @Validated UserReq userReq) {
log.info("id is {}", userReq.getId());
log.info("name is {}", userReq.getName());
return new ResponseEntity<>(HttpStatus.OK);
}
@Data
public class UserReq {
@IdCheck
private String id;
@NameCheck
private String name;
}
postman请求结果: