1、自动配置
springboot中早已默认集成hibernate-validator,首先看下自动配置是如何配置validator的。
ValidationAutoConfiguration
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({ExecutableValidator.class})
@ConditionalOnResource(
resources = {"classpath:META-INF/services/javax.validation.spi.ValidationProvider"}
)
@Import({PrimaryDefaultValidatorPostProcessor.class})
public class ValidationAutoConfiguration {
public ValidationAutoConfiguration() {
}
@Bean
@Role(2)
@ConditionalOnMissingBean({Validator.class})
public static LocalValidatorFactoryBean defaultValidator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
MessageInterpolatorFactory interpolatorFactory = new MessageInterpolatorFactory();
factoryBean.setMessageInterpolator(interpolatorFactory.getObject());
return factoryBean;
}
@Bean
@ConditionalOnMissingBean
public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment, @Lazy Validator validator) {
MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
boolean proxyTargetClass = ((Boolean)environment.getProperty("spring.aop.proxy-target-class", Boolean.class, Boolean.valueOf(true))).booleanValue();
processor.setProxyTargetClass(proxyTargetClass);
processor.setValidator(validator);
return processor;
}
}
配置都是带ConditionalOnMissingBean,也就是说我们可以自定义。例如为validator增加快速失败的配置,校验过程只要有一个错误就立即返回,而不是默认的校验完所有再返回。
接下来看看MethodValidationPostProcessor
实现了接口InitializingBean,在bean初始化的时候手动创建了一个aop的切点(Pointcut)和通知器(advisor),定义了一个Advice——MethodValidationInterceptor。(aop部分参见spring-core#aop)
@Override
public void afterPropertiesSet() {
Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
}
/**
* Create AOP advice for method validation purposes, to be applied
* with a pointcut for the specified 'validated' annotation.
* @param validator the JSR-303 Validator to delegate to
* @return the interceptor to use (typically, but not necessarily,
* a {@link MethodValidationInterceptor} or subclass thereof)
* @since 4.2
*/
protected Advice createMethodValidationAdvice(@Nullable Validator validator) {
return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
}
查看切点Pointcut的默认值:
private Class<? extends Annotation> validatedAnnotationType = Validated.class;
也就是说该aop仅对注解@Validated生效。MethodValidationInterceptor才是真正实现参数校验功能的。
2、跟踪一个接口
从MethodValidationInterceptor开始断点,跟踪一个接口的参数校验错误是如何返回的。
2.1获取校验分组
获取方法上是否有@Validated,若没有则找类上是否有@Validated注解,然后返回其中的value值,@Validated的value就表示校验分组。
protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
Validated validatedAnn = AnnotationUtils.findAnnotation(invocation.getMethod(), Validated.class);
if (validatedAnn == null) {
validatedAnn = AnnotationUtils.findAnnotation(invocation.getThis().getClass(), Validated.class);
}
return (validatedAnn != null ? validatedAnn.value() : new Class<?>[0]);
}
2.2 validateParameters
2.2.1 检查参数是否有限制条件
boolean hasConstraints = false;
for ( ConstraintMetaData constraintMetaData : constraintMetaDataSet ) {
boolean elementHasConstraints = constraintMetaData.isCascading() || constraintMetaData.isConstrained();
hasConstraints |= elementHasConstraints;
if ( constraintMetaData.getKind() == ElementKind.PROPERTY ) {
propertyMetaDataSet.add( (PropertyMetaData) constraintMetaData );
}
else {
ExecutableMetaData executableMetaData = (ExecutableMetaData) constraintMetaData;
if ( elementHasConstraints ) {
executableMetaDataSet.add( executableMetaData );
}
else {
tmpUnconstrainedExecutables.addAll( executableMetaData.getSignatures() );
}
}
}
没有则说明不要校验。
2.2.2 根据groups计算校验的顺序
groups为空时,默认设为DEFAULT_GROUPS
private static final Collection<Class<?>> DEFAULT_GROUPS = Collections.<Class<?>>singletonList( Default.class );
public ValidationOrder getValidationOrder(Collection<Class<?>> groups) {
if ( groups == null || groups.size() == 0 ) {
throw LOG.getAtLeastOneGroupHasToBeSpecifiedException();
}
// HV-621 - if we deal with the Default group we return the default ValidationOrder. No need to
// process Default as other groups which saves several reflection calls (HF)
if ( groups.size() == 1 && groups.contains( Default.class ) ) {
return ValidationOrder.DEFAULT_GROUP;
}
for ( Class<?> clazz : groups ) {
//分组的定义必须要是Interface
if ( !clazz.isInterface() ) {
throw LOG.getGroupHasToBeAnInterfaceException( clazz );
}
}
DefaultValidationOrder validationOrder = new DefaultValidationOrder();
for ( Class<?> clazz : groups ) {
if ( Default.class.equals( clazz ) ) { // HV-621
validationOrder.insertGroup( Group.DEFAULT_GROUP );
}
else if ( isGroupSequence( clazz ) ) {
insertSequence( clazz, clazz.getAnnotation( GroupSequence.class ).value(), true, validationOrder );
}
else {
Group group = new Group( clazz );
validationOrder.insertGroup( group );
insertInheritedGroups( clazz, validationOrder );
}
}
return validationOrder;
}
可以使用@GroupSequence指定分组的顺序
/**
* 新增校验组
*/
public interface AddGroup {
}
/**
* 更新校验组
*/
public interface UpdateGroup {
}
/**
* 定义校验顺序
*/
@GroupSequence({AddGroup.class, UpdateGroup.class})
public interface Group {
}
DefaultValidationOrder数据结构
public final class DefaultValidationOrder implements ValidationOrder {
private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
/**
* The list of single groups to be used this validation.
*/
private List<Group> groupList;
/**
* The different sequences for this validation. The map contains the sequences mapped to their sequence
* name.
*/
private Map<Class<?>, Sequence> sequenceMap;
......
}
2.2.3 根据分组顺序依次执行validateParametersForGroup
若是快速失败模式,只要有一个分组失败了就立即返回。
执行单个分组校验validateParametersForSingleGroup
private <T> void validateParametersForSingleGroup(ValidationContext<T> validationContext, Object[] parameterValues, ExecutableMetaData executableMetaData, Class<?> currentValidatedGroup) {
if ( !executableMetaData.getCrossParameterConstraints().isEmpty() ) {
ValueContext<T, Object> valueContext = getExecutableValueContext(
validationContext.getRootBean(), executableMetaData, executableMetaData.getValidatableParametersMetaData(), currentValidatedGroup
);
// 1. validate cross-parameter constraints
validateMetaConstraints( validationContext, valueContext, parameterValues, executableMetaData.getCrossParameterConstraints() );
if ( shouldFailFast( validationContext ) ) {
return;
}
}
ValueContext<T, Object> valueContext = getExecutableValueContext(
validationContext.getRootBean(), executableMetaData, executableMetaData.getValidatableParametersMetaData(), currentValidatedGroup
);
// 2. validate parameter constraints
for ( int i = 0; i < parameterValues.length; i++ ) {
ParameterMetaData parameterMetaData = executableMetaData.getParameterMetaData( i );
Object value = parameterValues[i];
if ( value != null ) {
Class<?> valueType = value.getClass();
if ( parameterMetaData.getType() instanceof Class && ( (Class<?>) parameterMetaData.getType() ).isPrimitive() ) {
valueType = ReflectionHelper.unBoxedType( valueType );
}
if ( !TypeHelper.isAssignable(
TypeHelper.getErasedType( parameterMetaData.getType() ),
valueType
) ) {
throw LOG.getParameterTypesDoNotMatchException(
valueType,
parameterMetaData.getType(),
i,
validationContext.getExecutable()
);
}
}
validateMetaConstraints( validationContext, valueContext, parameterValues, parameterMetaData );
if ( shouldFailFast( validationContext ) ) {
return;
}
}
}
对每个参数调用validateMetaConstraints进行校验,并判断是否为快速失败模式。
根据参数上的校验注解和参数类型,找到对应的validator,调用其isValid方法。例如我对一个String类型的参数添加校验@NotEmpty,则这里的validator为NotEmptyValidatorForCharSequence。
ComposingConstraintTree的validateConstraints
@Override
protected <T> void validateConstraints(ValidationContext<T> validationContext,
ValueContext<?, ?> valueContext,
Set<ConstraintViolation<T>> constraintViolations) {
CompositionResult compositionResult = validateComposingConstraints(
validationContext, valueContext, constraintViolations
);
Set<ConstraintViolation<T>> localViolations;
// After all children are validated the actual ConstraintValidator of the constraint itself is executed
if ( mainConstraintNeedsEvaluation( validationContext, constraintViolations ) ) {
if ( LOG.isTraceEnabled() ) {
LOG.tracef(
"Validating value %s against constraint defined by %s.",
valueContext.getCurrentValidatedValue(),
descriptor
);
}
// find the right constraint validator
ConstraintValidator<B, ?> validator = getInitializedConstraintValidator( validationContext, valueContext );
// create a constraint validator context
ConstraintValidatorContextImpl constraintValidatorContext = new ConstraintValidatorContextImpl(
validationContext.getParameterNames(),
validationContext.getClockProvider(),
valueContext.getPropertyPath(),
descriptor,
validationContext.getConstraintValidatorPayload()
);
// validate
localViolations = validateSingleConstraint(
validationContext,
valueContext,
constraintValidatorContext,
validator
);
// We re-evaluate the boolean composition by taking into consideration also the violations
// from the local constraintValidator
if ( localViolations.isEmpty() ) {
compositionResult.setAtLeastOneTrue( true );
}
else {
compositionResult.setAllTrue( false );
}
}
else {
localViolations = Collections.emptySet();
}
if ( !passesCompositionTypeRequirement( constraintViolations, compositionResult ) ) {
prepareFinalConstraintViolations(
validationContext, valueContext, constraintViolations, localViolations
);
}
}
getInitializedConstraintValidator正是在寻找当前校验对应的validator
protected final <T> ConstraintValidator<A, ?> getInitializedConstraintValidator(ValidationContext<T> validationContext, ValueContext<?, ?> valueContext) {
ConstraintValidator<A, ?> validator;
if ( validationContext.getConstraintValidatorFactory() == validationContext.getConstraintValidatorManager().getDefaultConstraintValidatorFactory()
&& validationContext.getConstraintValidatorInitializationContext() == validationContext.getConstraintValidatorManager()
.getDefaultConstraintValidatorInitializationContext() ) {
validator = constraintValidatorForDefaultConstraintValidatorFactoryAndInitializationContext;
if ( validator == null ) {
synchronized ( this ) {
validator = constraintValidatorForDefaultConstraintValidatorFactoryAndInitializationContext;
if ( validator == null ) {
validator = getInitializedConstraintValidator( validationContext );
constraintValidatorForDefaultConstraintValidatorFactoryAndInitializationContext = validator;
}
}
}
}
else {
// For now, we don't cache the result in the ConstraintTree if we don't use the default constraint validator
// factory. Creating a lot of CHM for that cache might not be a good idea and we prefer being conservative
// for now. Note that we have the ConstraintValidatorManager cache that mitigates the situation.
// If you come up with a use case where it makes sense, please reach out to us.
validator = getInitializedConstraintValidator( validationContext );
}
if ( validator == DUMMY_CONSTRAINT_VALIDATOR ) {
throw getExceptionForNullValidator( validatedValueType, valueContext.getPropertyPath().asString() );
}
return validator;
}
真正调用validator.isValid
protected final <T, V> Set<ConstraintViolation<T>> validateSingleConstraint(ValidationContext<T> executionContext,
ValueContext<?, ?> valueContext,
ConstraintValidatorContextImpl constraintValidatorContext,
ConstraintValidator<A, V> validator) {
boolean isValid;
try {
@SuppressWarnings("unchecked")
V validatedValue = (V) valueContext.getCurrentValidatedValue();
isValid = validator.isValid( validatedValue, constraintValidatorContext );
}
catch (RuntimeException e) {
if ( e instanceof ConstraintDeclarationException ) {
throw e;
}
throw LOG.getExceptionDuringIsValidCallException( e );
}
if ( !isValid ) {
//We do not add these violations yet, since we don't know how they are
//going to influence the final boolean evaluation
return executionContext.createConstraintViolations(
valueContext, constraintValidatorContext
);
}
return Collections.emptySet();
}
3、如何根据参数上的校验注解最终准确找到validator
刚才的getInitializedConstraintValidator一步一步深入后会发现一个叫findMatchingValidatorDescriptor的方法。以@NotEmpty为例
private <A extends Annotation> ConstraintValidatorDescriptor<A> findMatchingValidatorDescriptor(ConstraintDescriptorImpl<A> descriptor, Type validatedValueType) {
//descriptor.getMatchingConstraintValidatorDescriptors()表示@NotEmpty对应的所有validator,availableValidatorDescriptors以被校验参数类型为key,validator为value
Map<Type, ConstraintValidatorDescriptor<A>> availableValidatorDescriptors = TypeHelper.getValidatorTypes(
descriptor.getAnnotationType(),
descriptor.getMatchingConstraintValidatorDescriptors()
);
//判断当前校验的类型是否存在类型匹配的validator
List<Type> discoveredSuitableTypes = findSuitableValidatorTypes( validatedValueType, availableValidatorDescriptors.keySet() );
//匹配结果为多个时,移除所有的派生类型,只保留一个
resolveAssignableTypes( discoveredSuitableTypes );
if ( discoveredSuitableTypes.size() == 0 ) {
return null;
}
if ( discoveredSuitableTypes.size() > 1 ) {
throw LOG.getMoreThanOneValidatorFoundForTypeException( validatedValueType, discoveredSuitableTypes );
}
Type suitableType = discoveredSuitableTypes.get( 0 );
//返回匹配的validator
return availableValidatorDescriptors.get( suitableType );
}
查看NotEmptyValidatorForCharSequence的源码
public class NotEmptyValidatorForCharSequence implements ConstraintValidator<NotEmpty, CharSequence> {
/**
* Checks the character sequence is not {@code null} and not empty.
*
* @param charSequence the character sequence to validate
* @param constraintValidatorContext context in which the constraint is evaluated
* @return returns {@code true} if the character sequence is not {@code null} and not empty.
*/
@Override
public boolean isValid(CharSequence charSequence, ConstraintValidatorContext constraintValidatorContext) {
if ( charSequence == null ) {
return false;
}
return charSequence.length() > 0;
}
}
实现接口ConstraintValidator的时候声明了注解的type和被校验参数的类型。
可以发现NotEmpty的validator有十几个实现类,根据type(NotEmpty)和被校验参数类型(String)可以唯一的定位到某一个validator(NotEmptyValidatorForCharSequence)。