Spring Validation参数校验没那么简单,原理深度剖析!
本文会详细介绍 Spring Validation 各种场景下的最佳实践及其实现原理,死磕到底!
且看Spring Validation参数校验没那么简单,原理深度剖析!
简单使用
/strong>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-validatorartifactId>
<version>6.0.1.Finalversion>
dependency>
requestBody 参数校验
public class UserDTO {
private Long userId;
private String userName;
private String account;
private String password;
}
public Result saveUser(
UserDTO userDTO) {
// 校验通过,才会执行业务逻辑处理
return Result.ok();
}
requestParam/PathVariable
参数校验class UserController { // 路径变量 @GetMapping("{userId}") public Result detail(@PathVariable("userId") @Min(10000000000000000L) Long userId) { // 校验通过,才会执行业务逻辑处理 UserDTO userDTO = new UserDTO(); userDTO.setUserId(userId); userDTO.setAccount("11111111111111111"); userDTO.setUserName("xixi"); userDTO.setAccount("11111111111111111"); return Result.ok(userDTO); } // 查询参数 @GetMapping("getByAccount") public Result getByAccount(@Length(min = 6, max = 20) @NotNull String account) { // 校验通过,才会执行业务逻辑处理 UserDTO userDTO = new UserDTO(); userDTO.setUserId(10000000000000003L); userDTO.setAccount(account); userDTO.setUserName("xixi"); userDTO.setAccount("11111111111111111"); return Result.ok(userDTO); }}
public class CommonExceptionHandler {
public Result
handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
BindingResult bindingResult = ex.getBindingResult();
StringBuilder sb = new StringBuilder("校验失败:");
for (FieldError fieldError :
bindingResult.getFieldErrors()) {
sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
}
String msg = sb.toString();
return Result.fail(BusinessCode.参数校验失败, msg);
}
public Result
handleConstraintViolationException(ConstraintViolationException ex) {
return Result.fail(BusinessCode.参数校验失败,
ex.getMessage());
}
}
进阶使用 分组效验
@Data
public class UserDTO {
@Min(value = 10000000000000000L, groups = Update.class)
private Long userId;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String userName;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String account;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String password;
/**
* 保存的时候校验分组
*/
public interface Save {
}
/**
* 更新的时候校验分组
*/
public interface Update {
}
}
public Result saveUser(
UserDTO userDTO) {
// 校验通过,才会执行业务逻辑处理
return Result.ok();
}
public Result updateUser(
UserDTO userDTO) {
// 校验通过,才会执行业务逻辑处理
return Result.ok();
}
嵌套校验
@Data
public class UserDTO {
@Min(value = 10000000000000000L, groups = Update.class)
private Long userId;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String userName;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String account;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 6, max = 20, groups = {Save.class, Update.class})
private String password;
@NotNull(groups = {Save.class, Update.class})
@Valid
private Job job;
@Data
public static class Job {
@Min(value = 1, groups = Update.class)
private Long jobId;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String jobName;
@NotNull(groups = {Save.class, Update.class})
@Length(min = 2, max = 10, groups = {Save.class, Update.class})
private String position;
}
/**
* 保存的时候校验分组
*/
public interface Save {
}
/**
* 更新的时候校验分组
*/
public interface Update {
}
}
public class ValidationList<E> implements List<E> {
// @Delegate是lombok注解
// 一定要加@Valid注解
public List list = new ArrayList<>();
// 一定要记得重写toString方法
public String toString() {
return list.toString();
}
}
public Result saveList(
ValidationList userList) {
// 校验通过,才会执行业务逻辑处理
return Result.ok();
}
public EncryptId {
// 默认错误消息
String message() default "加密id格式错误";
// 分组
Class[] groups() default {};
// 负载
Class[] payload() default {};
}
public class EncryptIdValidator implements
ConstraintValidator<EncryptId, String> {
private static final Pattern PATTERN = Pattern.compile("^[a-f\\d]{32,256}$");
public boolean isValid(String value,
ConstraintValidatorContext context) {
// 不为null才进行校验
if (value != null) {
Matcher matcher = PATTERN.matcher(value);
return matcher.find();
}
return true;
}
}
编程式校验
private javax.validation.Validator globalValidator;
// 编程式校验
public Result saveWithCodingValidate( UserDTO userDTO) {
Set<constraintviolation> validate =
globalValidator.validate(userDTO, UserDTO.Save.class);
// 如果校验通过,validate为空;否则,validate包含未校验通过项
if (validate.isEmpty()) {
// 校验通过,才会执行业务逻辑处理
} else {
for (ConstraintViolation userDTOConstraintViolation : validate) {
// 校验失败,做其它逻辑
System.out.println(userDTOConstraintViolation);
}
}
return Result.ok();
}
快速失败(Fail Fast)
public Validator validator() {
ValidatorFactory validatorFactory =
Validation.byProvider(HibernateValidator.class)
.configure()
// 快速失败模式
.failFast(true)
.buildValidatorFactory();
return validatorFactory.getValidator();
}
@Valid 和 @Validated 区别
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
实现原理
requestBody参数校验实现原理
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
//将请求数据封装到DTO对象中
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name =
Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
WebDataBinder binder =
binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
// 执行数据校验
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() &&
isBindExceptionRequired(binder, parameter)) {
throw new
MethodArgumentNotValidException(parameter,
binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name,
binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
}
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
// 获取参数注解,比如@RequestBody、@Valid、@Validated
Annotation[] annotations =
parameter.getParameterAnnotations();
for (Annotation ann : annotations) {
// 先尝试获取@Validated注解
Validated validatedAnn =
AnnotationUtils.getAnnotation(ann, Validated.class);
//如果直接标注了@Validated,那么直接开启校验。
//如果没有,那么判断参数前是否有Valid起头的注解。
if (validatedAnn != null ||
ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ?
validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof
Object[] ?
(Object[]) hints : new Object[] {hints}); //执行校验
binder.validate(validationHints);
break;
}
}
}
public void validate(Object target, Errors errors, Object...
validationHints) {
if (this.targetValidator != null) {
processConstraintViolations(
//此处调用Hibernate Validator执行真正的校验
this.targetValidator.validate(target,
asValidationGroups(validationHints)), errors);
}
}
方法级别的参数校验实现原理
public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessorimplements InitializingBean { public void afterPropertiesSet() { //为所有`@Validated`标注的Bean创建切面 Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true); //创建Advisor进行增强 this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator)); } //创建Advice,本质就是一个方法拦截器 protected Advice createMethodValidationAdvice(@Nullable Validator validator) { return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor()); }}
public class MethodValidationInterceptor implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { //无需增强的方法,直接跳过 if (isFactoryBeanMetadataMethod(invocation.getMethod())) { return invocation.proceed(); } //获取分组信息 Class[] groups = determineValidationGroups(invocation); ExecutableValidator execVal = this.validator.forExecutables(); Method methodToValidate = invocation.getMethod(); Set<constraintviolation> result; try { //方法入参校验,最终还是委托给Hibernate Validator来校验 result = execVal.validateParameters( invocation.getThis(), methodToValidate, invocation.getArguments(), groups); } catch (IllegalArgumentException ex) { ... } //有异常直接抛出 if (!result.isEmpty()) { throw new ConstraintViolationException(result); } //真正的方法调用 Object returnValue = invocation.proceed(); //对返回值做校验,最终还是委托给Hibernate Validator来校验 result = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups); //有异常直接抛出 if (!result.isEmpty()) { throw new ConstraintViolationException(result); } return returnValue; }}
下是我们校区的地址和联系方式:
北大青鸟贵州大数据学院白云校区
贵阳市白云区白云南路895号
北大青鸟高新大学生实训校区
贵阳市观山湖区长岭南路33号国家大数据(贵州)综合试验区四楼