前言

最近维护一个老项目,项目使用最原始的Servlet,项目中充斥着各种类似判空的简单校验,为了减少重复代码,因此需要手动引入 Java 的 Bean Validation。

Java Bean Validation作为一个规范,更多的是定义一些标准化的接口,日常使用中我们常常引入HIbernate Validator实现。在不关心具体实现的情况下校验参数时经常使用的代码如下:

Validator validator = Validation.byDefaultProvider().configure().buildValidatorFactory().getValidator();
Set<ConstraintViolation<Object>> violations = validator.validate(new Object());

那么不免会产生疑问,上述获取的Validator是单例吗?或者说ValidatorFactory有对Validator进行缓存吗?如果进行了缓存那么在高并发的情况下就可以减少对象的创建,提高应用的性能。

带着这个问题,我们就需要对Java Bean Validation 进行深入了解。网络上的文章大多是一些零碎的知识点及简单的使用,通过对 JSR-303 文档的阅读及Bean Validation源码的查看总结出本篇,以期解答上述问题,并深入了解 Bean Validation。

Bean Validation 背景

校验数据是贯穿整个应用程序的一项常见任务,从表示层到持久层每个层可能会实现相同的校验逻辑,这样就会非常耗时且容易出错。为了避免在每个层中重复这些校验,开发人员通常将校验逻辑直接绑定到实体类中,将实体类与校验代码(实际上是类本身的元数据)混杂在一起。

Bean Validation 作为一种规范,由 Hibernate Validator 的高级开发人员 Emmanuel Bernard 领导,目前 Bean Validation 规范已经出了三版,分别是 JSR-303、JSR-349 及 JSR-380。它的目标是为java程序开发人员提供一个类级别的约束定义和校验能力。

约束

约束是约束注解和约束校验实现列表的组合,用于定义并对值的限制进行校验。

约束注解

约束注解可以应用在类、方法、属性或别的约束注解。约束注解还可以用在别的元素类型,但是约束校验时不必处理,约束注解的javadoc最好对支持的类型进行说明。

约束注解的属性

message、groups、payload是约束注解属性的保留名称,属性名称也不能以valid开头,同时也可以具有其他的属性。

  • 约束注解属性 message : 每个约束注解都必须定义一个类型为String的属性message,message的默认值建议作为resource bundule的键,以便支持国际化,推荐使用类名+注解的名称,如String message() default "{com.zzuhkp.constraint.MyConstraint.message}";

  • 约束注解属性 groups : 每个约束注解都必须定义一个默认值为空数组的属性groups,groups指定注解所属的组,用于控制校验的顺序或部分校验,如果没有指定groups,则约束注解所属的组为default,如Class<?>[] groups() default {};

  • 约束注解属性 payload : 每个约束注解都必须定义一个默认值为空数组的属性payload,payload指定约束注解所携带的附加元信息,payLoad的值可以被校验结果ConstraintViolation从ConstraintDescriptor中获取,如Class<? extends Payload>[] payload() default {};

示例:java内置的约束注解NotNull源码如下

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
@Documented
@Constraint(validatedBy = { })
public @interface NotNull {String message() default "{javax.validation.constraints.NotNull.message}";Class<?>[] groups() default { };Class<? extends Payload>[] payload() default { };/*** Defines several {@link NotNull} annotations on the same element.** @see javax.validation.constraints.NotNull*/@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })@Retention(RUNTIME)@Documented@interface List {NotNull[] value();}
}

重复约束注解

相同的约束注解可以使用不用的group,每个group的错误消息可能不同,为了支持这种需求,Bean Validation 也支持常规注解(没有被@Constraint标注),常规注解只要包含一个名为value,类型为约束注解数组的属性即可。

在 jdk 8 之前,java尚不支持重复注解,因此通常在约束注解中定义一个内部的注解List,List的属性value的类型为约束注解的数组,如上面的@NotNull注解。

示例如下:

public class RequestDTO {@Pattern(regexp = "^[0-9]+$", message = "手机号码只能为数字")@Pattern(regexp = "^1[34578]\\d{9}$", groups = RequestDTO.class, message = "手机号码格式有误")private String phone;}

组合注解

通过组合约束注解可以创建更高级的约束注解,组合注解即在约束注解上标注其他约束注解。使用方式如下:

  • 组合注解上使用其他约束注解,默认情况返回各约束注解的错误信息
  • 使用注解@ReportAsSingleViolation验证失败后将返回组合注解的message信息。
  • 组合注解上使用注解@OverridesAttribute覆盖被直接标注的其他的注解的属性。

示例如下:

@NotNull    //① 约束注解可以注解在组合注解上,默认情况返回组合中各个约束注解的错误信息
@Size(min = 1, max = 5, message = "大小有误")   //①
@Pattern(regexp = "", message = "格式有误1")    //①
@Pattern(regexp = "", message = "格式有误2")    //①
@ReportAsSingleViolation    //② 添加该注解后,验证失败返回组合注解(@Composition)的message()信息
@Constraint(validatedBy = {})
@Target({FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Composition {String message() default "组合注解错误提示";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};//③ 使用一个属性覆盖约束注解的多个属性@OverridesAttribute.List({@OverridesAttribute(constraint = Size.class, name = "min"),@OverridesAttribute(constraint = Size.class, name = "max")})int size() default 5;//④ 覆盖约束注解的属性(即标注@Composition的注解@Size的message属性)@OverridesAttribute(constraint = Size.class, name = "message")String sizeMessage() default "覆盖注解Size的错误提示";//⑤ 存在多个相同约束注解时,使用属性constraintIndex标识约束注解的索引位置@OverridesAttribute(constraint = Pattern.class, name = "message", constraintIndex = 0)String patternMessage1() default "覆盖第一个注解Pattern的错误提示";//⑤@OverridesAttribute(constraint = Pattern.class, name = "message", constraintIndex = 1)String patternMessage2() default "覆盖第二个注解Pattern的错误提示";}

约束校验实现

约束校验实现用于对给定类型执行给定约束注解的校验,可由约束注解中的@Constraint属性validatedBy指定,约束注解实现需要实现接口ConstraintValidator。也可以通过实现ConstraintValidatorFactory获取ConstraintValidator。
ConstraintValidator源码如下:

/*** @param <A> 实现要处理的约束注解类型* @param <T> 实现支持的目标类型,必须是非参数化类型或无界的通配符类型*/
public interface ConstraintValidator<A extends Annotation, T> {/*** #isValid调用之前进行调用,用于初始化* * @param 约束注解的实例*/default void initialize(A constraintAnnotation) {}/*** 校验逻辑的实现,需要考虑线程安全问题** @param 要校验的对象值,注意不能修改值的状态* @param 约束校验上下文,可用于重新创建ConstraintViolation** @return 是否通过校验*/boolean isValid(T value, ConstraintValidatorContext context);
}

ConstraintValidator 校验时使用的 ConstraintValidatorContext 可用于禁用默认 ConstraintViolation ,创建新的 ConstraintViolation,ConstraintViolation 包含违反约束的的元信息,ConstraintValidatorContext 部分源码如下:

public interface ConstraintValidatorContext {/*** 禁用默认ConstraintViolation对象生成*/void disableDefaultConstraintViolation();/*** @return 默认的消息模板*/String getDefaultConstraintMessageTemplate();/**** @since 2.0*/ClockProvider getClockProvider();/*** 使用给定的信息模板构建ConstraintViolation*/ConstraintViolationBuilder buildConstraintViolationWithTemplate(String messageTemplate);/*** 返回给定类型的实例* @since 1.1*/<T> T unwrap(Class<T> type);
}

约束目标

约束可以用于校验对象、字段、属性。

  • 校验对象:约束可以用在接口或类上,对运行时具体的对象进行校验。
  • 校验字段、属性:约束可以同时用在字段和属性,属性的read方法需要满足 JavaBean的规范(参见Java基础知识之JavaBean)。如果同时应用到字段和其对应的属性,则会重复校验。父类中的getter方法定义的约束会被子类重写的getter方法覆盖。
  • 递归校验:如果要对字段或属性的对象值内部的字段或属性进行递归校验,可以在字段或属性read方法上添加注解@Valid。如果对象的类型为数组、Collection、Map,则会循环校验每一项值,对于Map仅校验value,不校验key。

内建的约束

java bean validation 内建了一些常用的约束,整理日常开发使用的约束如下。

约束名称 目标类型 约束作用
Max BigDecimal、BigInteger、byte/short/int/long 及其包装类 注解的元素值必须小于等于给定值
Min BigDecimal、BigInteger、byte/short/int/long 及其包装类 注解的元素值必须大于等于给定值
NotBlank CharSequence 注解的元素不能为null,并且至少包含一个非空白字符
NotEmpty CharSequence、Collection、Map、Array 注解的元素不能为空
NotNull Object 注解的元素不能为null
Pattern CharSequence 注解的元素必须符合某个给定的正则表达式
Size CharSequence、Collection、Map、Array 注解的元素的大小必须符合给定的区间大小

自定义约束示例

1、定义约束

@Target({FIELD})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {CustomValidator.class})
public @interface IsTrue {String message() default "参数不为真";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};}

2、定义约束实现

public class CustomValidator implements ConstraintValidator<IsTrue, Boolean> {public CustomValidator(){}@Overridepublic void initialize(IsTrue constraintAnnotation) {}@Overridepublic boolean isValid(Boolean value, ConstraintValidatorContext context) {return value != null && value;}}

3、测试用例

public class Bean {@IsTrueprivate Boolean real;public Bean(Boolean real) {this.real = real;}
}public class BeanValidatorTest {public static void main(String[] args) {Bean bean = new Bean(false);Validator validator = Validation.buildDefaultValidatorFactory().getValidator();Set<ConstraintViolation<Bean>> violationSet = validator.validate(bean);violationSet.forEach(System.out::println);}
}

4、打印结果如下:

ConstraintViolationImpl{interpolatedMessage='参数不为真', propertyPath=real, rootBeanClass=class com.zzuhkp.validator.Bean, messageTemplate='参数不为真'}

分组和分组序列

分组

分组由java接口定义,通过约束注解的属性groups可以指定约束所属的分组,如果没有指定则默认分组为javax.validation.groups.Default,校验时可以根据分组进行校验,仅校验指定的分组。
示例:

public class BeanValidatorTest {static class Bean {@NotNull(message = "约束属于默认的Default分组")private String name;@NotNull(groups = Default.class, message = "约束属于默认的Default分组")private Integer sex;@NotNull(groups = CustomGroup.class, message = "约束属于自定义分组CustomGroup")private Integer age;}/*** 自定义group*/interface CustomGroup {}public static void main(String[] args) {Bean bean = new Bean();Validator validator = Validation.buildDefaultValidatorFactory().getValidator();Set<ConstraintViolation<Bean>> violationSet = validator.validate(bean, CustomGroup.class);}
}

打印如下:

ConstraintViolationImpl{interpolatedMessage='约束属于自定义分组CustomGroup', propertyPath=age, rootBeanClass=class com.zzuhkp.validator.BeanValidatorTest$Bean, messageTemplate='约束属于自定义分组CustomGroup'}

分组继承

分组可以继承其他分组,校验时同时会对继承分组的约束进行校验。
示例如下:

public class BeanValidatorTest {static class Bean {@NotNull(groups = SuperGroup.class, message = "约束属于SuperGroup分组")private String name;@NotNull(groups = Default.class, message = "约束属于默认的Default分组")private Integer sex;@NotNull(groups = CustomGroup.class, message = "约束属于自定义分组CustomGroup")private Integer age;}interface SuperGroup {}/*** 自定义group*/interface CustomGroup extends Default, SuperGroup {}public static void main(String[] args) {Bean bean = new Bean();Validator validator = Validation.buildDefaultValidatorFactory().getValidator();Set<ConstraintViolation<Bean>> violationSet = validator.validate(bean, CustomGroup.class);violationSet.forEach(System.out::println);}
}

打印结果为:

ConstraintViolationImpl{interpolatedMessage='约束属于默认的Default分组', propertyPath=sex, rootBeanClass=class com.zzuhkp.validator.BeanValidatorTest$Bean, messageTemplate='约束属于默认的Default分组'}
ConstraintViolationImpl{interpolatedMessage='约束属于自定义分组CustomGroup', propertyPath=age, rootBeanClass=class com.zzuhkp.validator.BeanValidatorTest$Bean, messageTemplate='约束属于自定义分组CustomGroup'}
ConstraintViolationImpl{interpolatedMessage='约束属于SuperGroup分组', propertyPath=name, rootBeanClass=class com.zzuhkp.validator.BeanValidatorTest$Bean, messageTemplate='约束属于SuperGroup分组'}

分组序列

分组中的约束校验时是无序的。在有些情况下,可能需要先校验一个分组,校验不通过时再校验另一个分组,例如后面的校验依赖前面的校验或者后面的校验比较消耗资源。为了达到按照顺序校验的目的,可以在分组上添加注解@GroupSequence使分组成为一个序列,并通过value属性指定序列中包含的分组。这样前面的分组校验不通过则不会校验后面的分组,值得注意的是序列中的分组可能是一个序列或者继承其他的分组,这种情况也会按照序列定义顺序校验。
示例如下:

public class BeanValidatorTest {static class Bean {@NotNull(groups = CustomGroupA.class, message = "约束属于CustomGroupA分组")private String name;@NotNull(groups = CustomGroupB.class, message = "约束属于CustomGroupB分组")private Integer sex;}interface CustomGroupA {}interface CustomGroupB {}@GroupSequence({CustomGroupA.class, CustomGroupB.class})interface CustomSequence {}public static void main(String[] args) {Bean bean = new Bean();Validator validator = Validation.buildDefaultValidatorFactory().getValidator();Set<ConstraintViolation<Bean>> violationSet = validator.validate(bean, CustomSequence.class);violationSet.forEach(System.out::println);}
}

打印结果如下:

ConstraintViolationImpl{interpolatedMessage='约束属于CustomGroupA分组', propertyPath=name, rootBeanClass=class com.zzuhkp.validator.BeanValidatorTest$Bean, messageTemplate='约束属于CustomGroupA分组'}

示例中定义了两个分组CustomGroupA和CustomGroupB,以及一个序列CustomSequence,序列包含了分组CustomGroupA和CustomGroupB。校验时前面分组CustomGroupA的校验未通过,CustomGroupB未进行校验。

重新定义默认分组

@GroupSequence注解除了可以标注接口,还可以标注在类上,这样@GroupSequence指定的组就构成了约束所属的默认分组。此时如果想校验未指定分组的约束,需要添加当前类到@GroupSequence.value中。自定义默认分组仅在当前类生效,不会传递到级联的其他类中。
示例如下:

public class BeanValidatorTest {@GroupSequence({CustomGroup.class, Bean.class})static class Bean {@NotNull(message = "约束属于Default分组")private String name;@NotNull(groups = CustomGroup.class, message = "约束属于CustomGroup分组")private Integer sex;}interface CustomGroup {}public static void main(String[] args) {Bean bean = new Bean();Validator validator = Validation.buildDefaultValidatorFactory().getValidator();Set<ConstraintViolation<Bean>> violationSet = validator.validate(bean);violationSet.forEach(System.out::println);}
}

打印结果如下:

ConstraintViolationImpl{interpolatedMessage='约束属于CustomGroup分组', propertyPath=sex, rootBeanClass=class com.zzuhkp.validator.BeanValidatorTest$Bean, messageTemplate='约束属于CustomGroup分组'}

校验时未指定分组,因为CustomGroup在@GroupSequence.value值的前面,因此先校验分组CustomGroup。

隐式分组

接口中Default分组的约束属于该接口分组。
示例如下:

    interface Super {@NotNull(message = "约束属于Default/Super分组")String getName();}static class Bean implements Super {@NotNull(message = "约束属于Default分组")private Integer sex;@Overridepublic String getName() {return null;}}

校验时分组为默认分组会同时校验接口中的getName方法和实现类中的sex成员变量,如果分组指定为Super.class,则仅校验接口中的getName方法。

约束校验顺序

对于给定的分组列表,前面的分组校验全部通过才会校验后面的分组。

约束校验顺序整体上并不固定,首先通过@Constraint.validateBy确定ConstraintValidator,然后调用isValid方法,如果校验通过则继续后面的约束校验,如果校验不通过则会把校验错误信息添加到ConstraintViolation对象。

能够确定的是按照不同约束类型的顺序进行校验,具体如下:

  1. 除非给定的约束已经在前面的分组中被处理,否则对匹配目标分组的可达字段(包括父类)执行字段级别的校验。
  2. 除非给定的约束已经在前面的分组中被处理,否则对匹配目标分组的可达get方法(包括接口和父类)执行get方法级别的校验。
  3. 除非给定的约束已经在前面的分组中被处理,否则执行类级别(包含父类和接口)的校验。
  4. 对于所有可达并且可级联的约束(包含接口和父类,如使用@Valid注解字段)进行校验。对于可级联的约束的校验,通过递归进行实现,为了避免无限循环,如果之前已经对关联的对象进行校验,则验证程序会忽略级联操作。

校验API

Validator

Validator是校验bean实例主要的API,实现必须是线程安全的,推荐ValidatorFactory对Validator进行缓存。
Validator的源码如下:

public interface Validator {/*** 校验对象上的所有约束*/<T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups);/*** 校验给定对象的属性的所有约束,属性是JavaBean的属性名称,忽略@Valid*/<T> Set<ConstraintViolation<T>> validateProperty(T object,String propertyName,Class<?>... groups);/*** 校验给定类型的属性的值,属性是JavaBean的属性名称,属性可以属于给定的bean类型或者其父类,忽略@Valid*/<T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType,String propertyName,Object value,Class<?>... groups);/*** 获取给定java bean的约束描述对象,注意返回值是不可变的*/BeanDescriptor getConstraintsForClass(Class<?> clazz);/*** 返回可以通过provider特定API访问的给定类型的实例*/<T> T unwrap(Class<T> type);/*** 返回校验方法和构造函数的参数和返回值的对象*/ExecutableValidator forExecutables();
}

ConstraintViolation

ConstraintViolation是描述单个约束错误的类,使用Validator进行校验会返回ConstraintViolation的集合。
ConstraintViolation源码如下:

public interface ConstraintViolation<T> {/*** 约束的错误信息*/String getMessage();/*** 约束的错误信息模板,即约束注解message属性指定的值*/String getMessageTemplate();/*** 正在进行校验的根对象*/T getRootBean();/*** 正在校验的根对象的类,对于方法是正在校验的对象的类,对于构造器是定义所在的类*/Class<T> getRootBeanClass();/*** 校验对象时返回校验的对象* 校属性时返回属性所属的对象* 校验给定的属性值时返回null* 校验方法参数或者返回值时返回执行方法的对象* 校验构造器参数时返回null* 校验构造器返回值时返回构造器创建的对象*/Object getLeafBean();/*** 如果当前对象是验证构造器或方法的参数被返回的,则返回构造器或者方法的参数数组*/Object[] getExecutableParameters();/*** 如果当前对象是验证构造器或方法的参数被返回的,则返回构造器或者方法的返回值*/Object getExecutableReturnValue();/*** 返回从根对象到当前校验值的属性路径*/Path getPropertyPath();/*** 返回未通过校验的值*/Object getInvalidValue();/*** 返回约束描述信息*/ConstraintDescriptor<?> getConstraintDescriptor();/*** 返回可以通过provider特定API访问的给定类型的实例*/<U> U unwrap(Class<U> type);
}

MessageInterpolator

MessageInterpolator可以将约束中的错误信息模板转换为可读的信息,直接接触它的场景应该并不多,简单了解即可。
信息参数是用{ }包起来的字符串,可用\进行转义。

默认MessageInterpolator

默认的MessageInterpolator转换步骤如下:

  1. 从信息模板中提取参数,从给定语言环境中获取名称为ValidationMessages的ResourceBundle,如果找到参数对应的值则替换参数,直到无法替换。
  2. 从默认的ResourceBundle中查找参数对应的值替换参数,和步骤1不同的是不会进行递归操作。
  3. 如果步骤2触发替换,则再次执行步骤1的流程,否则执行步骤4。
  4. 将参数对应的值替换参数。

自定义MessageInterpolator

自定义MessageInterpolator实现接口MessageInterpolator即可,实现必须是线程安全的,推荐将实现委托为默认的MessageInterpolator。
MessageInterpolator源码如下:

public interface MessageInterpolator {/*** 根据实现指定的默认语言信息转换信息模板*/String interpolate(String messageTemplate, Context context);/*** 使用指定的语音信息转换信息模板* @param messageTemplate 约束上给定的信息模板* @param context 上下文信息* @param locale 语言环境*/String interpolate(String messageTemplate, Context context,  Locale locale);/*** 上下文信息*/interface Context {/*** 获取约束描述信息*/ConstraintDescriptor<?> getConstraintDescriptor();/*** 获取被校验的对象*/Object getValidatedValue();/*** 返回可以通过provider特定API访问的给定类型的实例*/<T> T unwrap(Class<T> type);}
}

设置MessageInterpolator的方式如下:

  1. 通过javax.validation.Configuration.messageInterpolator指定MessageInterpolator。
  2. 通过javax.validation.ValidatorContext.messageInterpolator指定MessageInterpolator。

示例源码如下:

public class BeanValidatorTest {static class CustomMessageInterpolator implements MessageInterpolator {private MessageInterpolator delegate;public CustomMessageInterpolator(MessageInterpolator delegate) {this.delegate = delegate;}@Overridepublic String interpolate(String messageTemplate, Context context) {return delegate.interpolate(messageTemplate, context);}@Overridepublic String interpolate(String messageTemplate, Context context, Locale locale) {return delegate.interpolate(messageTemplate, context, locale);}}public static void main(String[] args) {Configuration<?> configuration = Validation.byDefaultProvider().configure();CustomMessageInterpolator interpolator = new CustomMessageInterpolator(configuration.getDefaultMessageInterpolator());//1、通过javax.validation.Configuration.messageInterpolator指定MessageInterpolatorValidatorFactory factory = configuration.messageInterpolator(interpolator).buildValidatorFactory();//2、通过javax.validation.ValidatorContext.messageInterpolator指定MessageInterpolatorValidator validator = factory.usingContext().messageInterpolator(interpolator).getValidator();}}

Bootstrapping API 概览

  • Validator:用于校验bean,实现必须是线程安全的。
  • ValidatorFactory: 创建并初始化Validator,使用者可缓存ValidatorFactory,ValidatorFactory可以缓存Validator。
  • ValidatorContext:创建Validator的上下文,可通过ValidatorFactory#usingContext方法获取,覆盖ValidatorFactory中的配置。
  • Configuration:接收配置信息,创建ValidatorFactory。
  • 配置信息
    • MessageInterpolator:将约束中的模板信息解析为实际的错误信息。
    • TraversableResolver:确定要校验的对象是否可达或可级联,实现必须是线程安全的。
    • ConstraintValidatorFactory:用于获取ConstraintValidator。
    • ParameterNameProvider:提供构造器或普通方法的参数名称列表。
    • ClockProvider:校验约束@Future和@Past时获取Clock判断当前时间。
  • ValidationProvider:SPI接口,创建通用的和特定的Configuration,构建ValidatorFactory。
  • ValidationProviderResolver:用于获取ValidationProviderResolver列表,默认实现是通过SPI机制获取。
  • Validation:bean validation的入口,用于获取ValidatorFactory。
    • GenericBootstrap:创建和ValidationProvider无关的Configuration。
    • ProviderSpecificBootstrap:创建和ValidationProvider自定义的Configuration。

各API之间的关系可参考如下示例代码:

        // GenericBootstrap bootstrap = Validation.byDefaultProvider();ProviderSpecificBootstrap<CustomConfiguration> bootstrap = Validation.byProvider(CustomValidationProvider.class);Configuration<?> configuration = bootstrap.providerResolver(validationProviderResolver).configure();ValidatorFactory factory = configuration.messageInterpolator(messageInterpolator).constraintValidatorFactory(constraintValidatorFactory).traversableResolver(traversableResolver).buildValidatorFactory();Validator validator = factory.usingContext().messageInterpolator(messageInterpolator).constraintValidatorFactory(constraintValidatorFactory).traversableResolver(traversableResolver).getValidator();validator = factory.getValidator();

问题解答

1、ValidatorFactory是否缓存了Validator?

开篇提出了Validator是否缓存的问题,在此加以解答。

通过前面章节的了解,我们知道Java Bean Validation 是一种规范,它只是描述了ValidatorFactory可以缓存Validator,事实上是否缓存还是依赖于具体的实现。

跟踪 Hibernate Validator 的源码,ValidatorFactory#getValidator方法最终会调用org.hibernate.validator.internal.engine.ValidatorFactoryImpl#createValidator,该方法并未缓存Validator,但是缓存了创建Validator实现所需的元数据,因此创建Validator是相对轻量级的。如果不需要对ValidatorFactory做特殊的配置,还是建议在应用中缓存ValidatorFactory创建好的Validator。

2、为什么只需要引入Hibernate Validator,没有明确使用Hibernate Validator 也可以进行校验?
java中存在一种称为SPI的机制,可以从类路径中获取特定接口的实现,具体可参见前面的文章Java基础知识之SPI。

Validation.byDefaultProvider().configure()返回Configuration对象,#configure实现先获取ValidationProvider,然后通过ValidationProvider#createGenericConfiguration方法获取Configuration,获取ValidationProvider的过程便调用了方法GetValidationProviderListAction#loadProviders,该方法使用SPI机制加载类路径中/META-INF/javax.validation.spi.ValidationProvider文件中定义的ValidationProvider实现。Hibernate Validator 中定义了该文件,因此最终可以获取到Hibernate Validator 的实现。

3、踩坑记录

我们的项目之前未使用maven管理依赖,改为maven管理依赖使用hibernate validator 后服务无响应,无异常日志打印。通过排查发现产生依赖冲突,导致hibernate validator使用了一个较低版本的log4j,缺少log4j类中的一个字段,只有捕获Throwable才会打印日志。最终手动引入了一个较高版本的log4j解决。

总结

本篇先引入使用 bean validation 的问题,然后对 bean validation 中的约束、分组及相关API进行介绍,最后解答前面提出的问题。

参考:
JSR-303

<dependency><groupId>javax.validation</groupId><artifactId>validation-api</artifactId><version>2.0.1.Final</version>
</dependency>

Java Bean Validation 详解相关推荐

  1. java spring bean配置文件_Spring基于xml文件配置Bean过程详解

    这篇文章主要介绍了spring基于xml文件配置Bean过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 通过全类名来配置: class:be ...

  2. 孙鑫 java web_javaweb(code) 孙鑫老师的java web 开发详解的源代码 - 下载 - 搜珍网

    javaweb开发详解(code)/AppendixA/FirstPage.html javaweb开发详解(code)/AppendixA/form.html javaweb开发详解(code)/A ...

  3. Java内存溢出详解之Tomcat配置

    Java内存溢出详解 转自:http://elf8848.iteye.com/blog/378805 一.常见的Java内存溢出有以下三种: 1. java.lang.OutOfMemoryError ...

  4. java基础(十三)-----详解内部类——Java高级开发必须懂的

    java基础(十三)-----详解内部类--Java高级开发必须懂的 目录 为什么要使用内部类 内部类基础 静态内部类 成员内部类 成员内部类的对象创建 继承成员内部类 局部内部类 推荐博客 匿名内部 ...

  5. Java类加载机制详解【java面试题】

    Java类加载机制详解[java面试题] (1)问题分析: Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数 ...

  6. Java线程池详解学习:ThreadPoolExecutor

    Java线程池详解学习:ThreadPoolExecutor Java的源码下载参考这篇文章:Java源码下载和阅读(JDK1.8) - zhangpeterx的博客 在源码的目录java/util/ ...

  7. Java 线程池详解学习:FixedThreadPool,CachedThreadPool,ScheduledThreadPool...

    Java常用的线程池有FixedThreadPool和CachedThreadPool,我们可以通过查看他们的源码来进行学习. Java的源码下载参考这篇文章:Java源码下载和阅读(JDK1.8) ...

  8. 关于Java的Classpath详解

    关于Java的Classpath详解 Java 的新入门者对classpath往往比较困惑,为何在开发环境中能运行的东东出去就不好,或在外面运行的东东挺溜的进了开发环境就死菜. java的优点就是他是 ...

  9. java异常体系结构详解

    java异常体系结构详解 参考文章: (1)java异常体系结构详解 (2)https://www.cnblogs.com/hainange/p/6334042.html 备忘一下.

最新文章

  1. 【php】【psr】psr2 编码风格规范
  2. java Character类的一些简单的方法
  3. 【Leetcode】79.单词搜索
  4. JAVA API-----String类和StringBuffer类
  5. Asp.net生成缩略图
  6. c语言中规定,程序中各函数之间,C语言题库-函数_(参考).doc
  7. lock和synchronized的同步区别与选择
  8. jdk8 接口新特性
  9. linux c普通用户怎么判断键盘是否按动_网络没问题,电脑却无法联网怎么办?win10无法联网搞定方式举例...
  10. [转载]如何做一个出色的程序员
  11. 当个性化推荐遇上知识图谱.pdf(附下载链接)
  12. 【渝粤教育】电大中专跨境电子商务理论与实务 (8)作业 题库
  13. centos7完全卸载mysql_Centos7 完全卸载mysql
  14. 关于ucgui3.98(显示部分)移植
  15. 因子分解机(FM,FFM,DeepFM,libfm,xlearn)
  16. dev 居中_css的div垂直居中的方法,百分比div垂直居中
  17. 高频头极化角调整+用什么本振的高频头
  18. oracle数据如何采集,网页采集的数据如何导出到Oracle数据库 - 八爪鱼采集器
  19. [词汇] 十四、动词
  20. 计算机科技英语写作,英语科技写作

热门文章

  1. 数据中心的“灾备”指的是什么?
  2. 小程序如何在其他页面监听globalData中值的变化?
  3. 从一千万条短信中找出重复次数最多的前10条
  4. 《线性代数:行列式》: n 阶方阵行列式公式
  5. UVA 11205 - The broken pedometer
  6. stm32 lib库制作
  7. 机架服务器和群晖存文件对比,如何选购群晖nas网络存储服务器?
  8. 检测中质量监督常见的数个问题
  9. 终于把PEST分析模型给整明白了!
  10. PyGame弹珠游戏双人粗略版