0%

Spring和Hibernate Validator

简介

之前使用Spring结合Hibernate Validator来做接口校验经常由于使用姿势不对导致无法正常运行,在网上找到的文档也没有对我感兴趣的部分进行讲解,所以最近大致查看了下源码来了解下这部分信息。

背景知识

  • JSR380 这是一个java定义的一个校验标准接口在此之前还存在老版本JSR303

  • Hibernate Validator 对JSR380进行了实现

  • Spring Validator 是对Hibernate Validator 进行了包装使其和Spring完成整合,以下两个包分别为Java的标准校JSR380验接口,和Hibernate 的实现 注:Hibernate Validator 6.0版本实现的是JSR380之前为JSR303等版本,这里主要分析的是JSR380所以我采用的是6.0版本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    </dependency>

    <dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    </dependency>

核心概念

既然是基于JSR380 实现的校验框架,那我们必须了解一下JSR380一些核心的概念,从官网上查看JSR380文档,以下几个接口为最核心的接口

  • ConstraintValidator 此接口为校验的核心,所有的校验的逻辑都在这里,比如我们使用的@Min注解,其就有针对@Min注解所实现的校验逻辑代码MinValidatorForLong ,MinValidatorForDouble等代码分别为long类型和doubue类型来处理校验逻辑

  • Validator 这是JSR最主要的校验API,也是和使用方打交道的一个API,一下是JSR380文档提供的一个校验例子,我们获取Validator后直接调用validate方法即可完成校验,也就是说我们并不会和底层实现打交道,我们只要会使用Validator就可以使用JSR380校验框架

1
2
3
4
5
6
7
8
9
10
11
12
13
Author author = new Author();
author.setCompany( "ACME" );

List<String> tags = Arrays.asList( "a", "science fiction" );

Book book = new Book();
book.setTitle( "" );
book.setAuthor( author );

book.setTags( tags );

Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
Set<ConstraintViolation<Book>> constraintViolations = validator.validate( book );
  • ValidatorFactory 其实获取 Validator 的工程类

  • ExecutableValidator JSR380不仅提供了校验对象,和提供了校验方法参数和返回值,这个是我们日常之中使用最多的,其底层就是调用Validator 的forExecutables方法来获取的ExecutableValidator ,再调用其validateParameters方法去校验方法的参数返回值等,具体的大家可以查看接口文档

  • Validation 其类似一个启动类,其读取配置文件,获取JSR380的实现,它的buildDefaultValidatorFactory方法,帮我们方便的获取ValidatorFactory ,从而屏蔽了实现的细节。

代码分析

通过一些列debug和代码查找我找到了Spring Validate的入口MethodValidationPostProcessor,其很简单就是一个BeanFactoryProcessor,之前的文档已经说明了它在Spring的BeanFactory初始化后进行调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95

/**
* A convenient {@link BeanPostProcessor} implementation that delegates to a
* JSR-303 provider for performing method-level validation on annotated methods.
*
* <p>Applicable methods have JSR-303 constraint annotations on their parameters
* and/or on their return value (in the latter case specified at the method level,
* typically as inline annotation), e.g.:
*
* <pre class="code">
* public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2)
* </pre>
*
* <p>Target classes with such annotated methods need to be annotated with Spring's
* {@link Validated} annotation at the type level, for their methods to be searched for
* inline constraint annotations. Validation groups can be specified through {@code @Validated}
* as well. By default, JSR-303 will validate against its default group only.
*
* <p>As of Spring 5.0, this functionality requires a Bean Validation 1.1 provider.
*
* @author Juergen Hoeller
* @since 3.1
* @see MethodValidationInterceptor
* @see javax.validation.executable.ExecutableValidator
*/
@SuppressWarnings("serial")
public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor
implements InitializingBean {

private Class<? extends Annotation> validatedAnnotationType = Validated.class;

@Nullable
private Validator validator;


/**
* Set the 'validated' annotation type.
* The default validated annotation type is the {@link Validated} annotation.
* <p>This setter property exists so that developers can provide their own
* (non-Spring-specific) annotation type to indicate that a class is supposed
* to be validated in the sense of applying method validation.
* @param validatedAnnotationType the desired annotation type
*/
public void setValidatedAnnotationType(Class<? extends Annotation> validatedAnnotationType) {
Assert.notNull(validatedAnnotationType, "'validatedAnnotationType' must not be null");
this.validatedAnnotationType = validatedAnnotationType;
}

/**
* Set the JSR-303 Validator to delegate to for validating methods.
* <p>Default is the default ValidatorFactory's default Validator.
*/
public void setValidator(Validator validator) {
// Unwrap to the native Validator with forExecutables support
if (validator instanceof LocalValidatorFactoryBean) {
this.validator = ((LocalValidatorFactoryBean) validator).getValidator();
}
else if (validator instanceof SpringValidatorAdapter) {
this.validator = validator.unwrap(Validator.class);
}
else {
this.validator = validator;
}
}

/**
* Set the JSR-303 ValidatorFactory to delegate to for validating methods,
* using its default Validator.
* <p>Default is the default ValidatorFactory's default Validator.
* @see javax.validation.ValidatorFactory#getValidator()
*/
public void setValidatorFactory(ValidatorFactory validatorFactory) {
this.validator = validatorFactory.getValidator();
}


@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());
}

}

在afterPropertiesSet方法中我们看到起定义了一个AOP,它会去扫描带有@Validated注解的类

我们再进入MethodValidationInterceptor中查看下其AOP执行的逻辑,可以看到起就是使用validator.forExecutables方法来获取了JSR380 的ExecutableValidator 来做方法校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Override
@SuppressWarnings("unchecked")
public Object invoke(MethodInvocation invocation) throws Throwable {
// Avoid Validator invocation on FactoryBean.getObjectType/isSingleton
if (isFactoryBeanMetadataMethod(invocation.getMethod())) {
return invocation.proceed();
}

Class<?>[] groups = determineValidationGroups(invocation);

// Standard Bean Validation 1.1 API
ExecutableValidator execVal = this.validator.forExecutables();
Method methodToValidate = invocation.getMethod();
Set<ConstraintViolation<Object>> result;

try {
result = execVal.validateParameters(
invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
}
catch (IllegalArgumentException ex) {
// Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011
// Let's try to find the bridged method on the implementation class...
methodToValidate = BridgeMethodResolver.findBridgedMethod(
ClassUtils.getMostSpecificMethod(invocation.getMethod(), invocation.getThis().getClass()));
result = execVal.validateParameters(
invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
}
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}

Object returnValue = invocation.proceed();

result = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups);
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}

return returnValue;
}

那Hibernate Validator的实现又是在哪里被注入的呢?我们查看MethodValidationPostProcessor下的createMethodValidationAdvice方法,继续定位到MethodValidationInterceptor类看到其构造,我们就知道了其也是通过Validation来完成的初始化

1
2
3
public MethodValidationInterceptor() {
this(Validation.buildDefaultValidatorFactory());
}

我们继续往下寻找Validation调用的方法

1
2
3
public static ValidatorFactory buildDefaultValidatorFactory() {
return byDefaultProvider().configure().buildValidatorFactory();
}

进入byDefaultProvider().configure()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

@Override
public Configuration<?> configure() {
ValidationProviderResolver resolver = this.resolver == null ?
getDefaultValidationProviderResolver() :
this.resolver;

List<ValidationProvider<?>> validationProviders;
try {
validationProviders = resolver.getValidationProviders();
}
// don't wrap existing ValidationExceptions in another ValidationException
catch ( ValidationException e ) {
throw e;
}
// if any other exception occurs wrap it in a ValidationException
catch ( RuntimeException re ) {
throw new ValidationException( "Unable to get available provider resolvers.", re );
}

if ( validationProviders.isEmpty() ) {
String msg = "Unable to create a Configuration, because no Bean Validation provider could be found." +
" Add a provider like Hibernate Validator (RI) to your classpath.";
throw new NoProviderFoundException( msg );
}

Configuration<?> config;
try {
config = resolver.getValidationProviders().get( 0 ).createGenericConfiguration( this );
}
catch ( RuntimeException re ) {
throw new ValidationException( "Unable to instantiate Configuration.", re );
}

return config;
}

再进入getDefaultValidationProviderResolver方法,一直往下定位知道看到Validation的内部类GetValidationProviderListAction的loadProviders方法,这里 ServiceLoader.load 使用了JAVA提供的SPI技术,从配置文件中读取了Hibernate Validator的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private List<ValidationProvider<?>> loadProviders(ClassLoader classloader) {
ServiceLoader<ValidationProvider> loader = ServiceLoader.load( ValidationProvider.class, classloader );
Iterator<ValidationProvider> providerIterator = loader.iterator();
List<ValidationProvider<?>> validationProviderList = new ArrayList<>();
while ( providerIterator.hasNext() ) {
try {
validationProviderList.add( providerIterator.next() );
}
catch ( ServiceConfigurationError e ) {
// ignore, because it can happen when multiple
// providers are present and some of them are not class loader
// compatible with our API.
}
}
return validationProviderList;
}

我们去全局搜索找到了javax.validation.spi文件发现其配置了javax.validation.spi.ValidationProvider文件其注入配置了org.hibernate.validator.HibernateValidator完成了Hibernate Validator的注入

Hibernate Validator校验逻辑

这里首先说一个问题,在我第一次看见ConstraintValidator的时,我感觉很奇怪,既然其是实现验证代码的地方,那么你是如何去定位到对应的实现类的?比如被@Max注解的Long 类型应该去调用ConstraintValidator的实现类MinValidatorForLong ,因为依照我以往的套路其往往有一个support方法来去校验调用的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public interface ConstraintValidator<A extends Annotation, T> {

/**
* Initializes the validator in preparation for
* {@link #isValid(Object, ConstraintValidatorContext)} calls.
* The constraint annotation for a given constraint declaration
* is passed.
* <p>
* This method is guaranteed to be called before any use of this instance for
* validation.
* <p>
* The default implementation is a no-op.
*
* @param constraintAnnotation annotation instance for a given constraint declaration
*/
default void initialize(A constraintAnnotation) {
}

/**
* Implements the validation logic.
* The state of {@code value} must not be altered.
* <p>
* This method can be accessed concurrently, thread-safety must be ensured
* by the implementation.
*
* @param value object to validate
* @param context context in which the constraint is evaluated
*
* @return {@code false} if {@code value} does not pass the constraint
*/
boolean isValid(T value, ConstraintValidatorContext context);
}

于是我又找到了ConstraintHelper方法,其定义了各种ConstraintValidator的实现类,我们这里截取到最主要的代码,它其实把能处理@Min的ConstraintValidator都放在了一起,在调用时根据@Min注解的参数对象的类型来调用不同的ConstraintValidator

1
2
3
4
5
6
7
8
9
putConstraints( tmpConstraints, Min.class, Arrays.asList(
MinValidatorForBigDecimal.class,
MinValidatorForBigInteger.class,
MinValidatorForDouble.class,
MinValidatorForFloat.class,
MinValidatorForLong.class,
MinValidatorForNumber.class,
MinValidatorForCharSequence.class
) );

总结

Spring Validator 使用了AOP扫描@Validated注解来对Hibernate Validator进行了封装,并使用MethodValidationPostProcessor建其注入到Spring的生命周期中