1 概述
hibernate-validator提供了很多内置的校验注解(如@NotNull),还提供了可自定义校验注解的机制,这个对业务系统会比较方便。把一些通用的校验变成校验注解,方便各个业务使用,正是框架应该做的事情,本文了解一下自定义校验注解的机制。
2 原理
2.1 例子
先看一个例子,再根据例子来看原理。业务系统不少地方需要用到手机号,中国手机号有点特殊,它由国家码和11位的手机号码组成,要校验的话就得校验这两个信息都得合法。如果让每个业务都写这种校验,也是比较麻烦的,这个时候就可以用自定义校验注解的方式来提供便利。
1) 定义手机号类。
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Value;
@Schema(description = "手机号")
@Value
@AllArgsConstructor
public class MobilePhone {
@Schema(description = "国家码", requiredMode = Schema.RequiredMode.REQUIRED)
String countryCode;
@Schema(description = "手机号码", requiredMode = Schema.RequiredMode.REQUIRED)
String phoneCode;
}
2) 定义一个注解@MobilePhoneValid,注解里面带@Constraint标识。
@Target({ METHOD, FIELD })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {})
public @interface MobilePhoneValid {
String message() default "{mobile.phone.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
3) 定义一个ConstraintValidator
// ConstraintValidator泛型的第一个参数为注解,第二个参数为要校验的对象类型
public class MobilePhoneConstraintValidator implements ConstraintValidator<MobilePhoneValid, MobilePhone> {
private Pattern pattern = Pattern.compile("^1[3-9]\\d{9}$");
@Override
public boolean isValid(MobilePhone value, ConstraintValidatorContext context) {
if(value != null) {
return value.getCountryCode() != null && pattern.matcher(value.getPhoneCode()).matches();
}
return true;
}
}
4) 把校验注解和ConstraintValidator关联起来
// 在@Constraint的validatedBy属性指定ConstraintValidator类。
@Constraint(validatedBy = {MobilePhoneConstraintValidator.class})
public @interface MobilePhoneValid {}
5) 在属性中使用校验注解
public class GroupMember {
@Schema(description = "手机号码", implementation = GroupMemberGender.class)
@MobilePhoneValid
private MobilePhone mobilePhone;
}
自定义好注解和对应的ConstraintValidator之后,就普通的校验注解没有什么区别,说明这个机制设计得比较好。
2.2 查找自定义的ConstraintValidator
在查找请求参数对象哪里配置了校验相关的注解的时候,会把有校验相关的注解标记的字段/方法/类的信息封装到ConstraintDescriptorImpl中。比如用@NotNull注解标注了GroupMember对象的name属性时,ConstraintDescriptorImpl就包装了GroupMember对象的name属性的相关信息。
// 源码位置:org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl
public ConstraintDescriptorImpl(ConstraintHelper constraintHelper,
// 如果是参数对象的属性,则constrainable是一个JavaBeanField,即封装了这个对象属性信息
Constrainable constrainable,
// 校验相关的注解信息,如@NotNull注解
ConstraintAnnotationDescriptor<T> annotationDescriptor,
// 指明注解标注的类型,如果是对象的属性,constraintLocationKind为FIELD
ConstraintLocationKind constraintLocationKind,
Class<?> implicitGroup,
ConstraintOrigin definedOn,
ConstraintType externalConstraintType) {
this.annotationDescriptor = annotationDescriptor;
this.constraintLocationKind = constraintLocationKind;
this.definedOn = definedOn;
this.isReportAsSingleInvalidConstraint = annotationDescriptor.getType().isAnnotationPresent(
ReportAsSingleViolation.class
);
this.groups = buildGroupSet( annotationDescriptor, implicitGroup );
this.payloads = buildPayloadSet( annotationDescriptor );
this.valueUnwrapping = determineValueUnwrapping( this.payloads, constrainable, annotationDescriptor.getType() );
this.validationAppliesTo = determineValidationAppliesTo( annotationDescriptor );
this.constraintValidatorClasses = constraintHelper.getAllValidatorDescriptors( annotationDescriptor.getType() )
.stream()
.map( ConstraintValidatorDescriptor::getValidatorClass )
.collect( Collectors.collectingAndThen( Collectors.toList(), CollectionHelper::toImmutableList ) );
// 1. 使用constraintHelper找注解对应的ConstraintValidator,constraintHelper为org.hibernate.validator.internal.metadata.core.ConstraintHelper
List<ConstraintValidatorDescriptor<T>> crossParameterValidatorDescriptors = CollectionHelper.toImmutableList( constraintHelper.findValidatorDescriptors(
annotationDescriptor.getType(),
ValidationTarget.PARAMETERS
) );
List<ConstraintValidatorDescriptor<T>> genericValidatorDescriptors = CollectionHelper.toImmutableList( constraintHelper.findValidatorDescriptors(
annotationDescriptor.getType(),
ValidationTarget.ANNOTATED_ELEMENT
) );
if ( crossParameterValidatorDescriptors.size() > 1 ) {
throw LOG.getMultipleCrossParameterValidatorClassesException( annotationDescriptor.getType() );
}
this.constraintType = determineConstraintType(
annotationDescriptor.getType(),
constrainable,

最低0.47元/天 解锁文章
1463

被折叠的 条评论
为什么被折叠?



