加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Java > 正文

深入了解数据校验:Java Bean Validation 2.0

发布时间:2020-12-15 08:00:59 所属栏目:Java 来源:网络整理
导读:Java Bean Validation JSR是 Java Specification Requests 的缩写,意思是Java 规范提案。关于数据校验这块,最新的是 JSR380 ,也就是我们常说的 Bean Validation 2.0 。 Bean Validation是一个通过配置注解来验证参数的框架,它包含两部分Bean Validation

Java Bean Validation

JSR是Java Specification Requests的缩写,意思是Java 规范提案。关于数据校验这块,最新的是JSR380,也就是我们常说的Bean Validation 2.0

Bean Validation是一个通过配置注解来验证参数的框架,它包含两部分Bean Validation API(规范)和Hibernate Validator(实现)。

现在绝大多数coder使用者其实都还在使用Bean Validation 1.1,毕竟一般来说它已经够用了~
本文会介绍Bean Validation 2.0提供的一些实用的新东西,毕竟Java8现在已成为主流,完全可以使用了~

要想使用它,首先就得导包嘛~根据经验,和JCache类似Java只提供了规范,并没有提供实现,所以我们可以先找到它的API包然后导入:

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

兼容性表格

Bean Validation Hibernate Validation JDK Spring Boot
1.1 5.4 + 6+ 1.5.x
2.0 6.0 + 8+ 2.0.x

关于Bean Validation 2.0的关注点(新特性)

对于Java Bean Validation的实现落地产品就没啥好选的,导入Hibernate Validator(最新版本)吧:

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.17.Final</version>
</dependency>

定义一个待校验的普通JavaBean:

@Getter
@Setter
@ToString
public class Person {

    // 错误消息message是可以自定义的
    @NotNull(message = "名字不能为null")
    public String name;
    @Positive
    public Integer age;

    @NotNull
    @NotEmpty
    private List<@Email String> emails;
    @Future
    private Date start;

}

书写测试用例:

    public static void main(String[] args) {
        Person person = new Person();
        //person.setName("fsx");
        person.setAge(-1);
        // email校验:虽然是List都可以校验哦
        person.setEmails(Arrays.asList("[email?protected]","[email?protected]","aaa.com"));
        //person.setStart(new Date()); //start 需要是一个将来的时间: Sun Jul 21 10:45:03 CST 2019
        //person.setStart(new Date(System.currentTimeMillis() + 10000)); //校验通过

        // 对person进行校验然后拿到结果(显然使用时默认的校验器)   会保留下校验失败的消息
        Set<ConstraintViolation<Person>> result = Validation.buildDefaultValidatorFactory().getValidator().validate(person);
        // 对结果进行遍历输出
        result.stream().map(v -> v.getPropertyPath() + " " + v.getMessage() + ": " + v.getInvalidValue())
                .forEach(System.out::println);
    }

运行,报错啦:

Caused by: java.lang.ClassNotFoundException: javax.el.ELManager
    at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
...

可以看到运行必须依赖于javax.el这个包。

我自己来解释为何需要导入EL包这个问题:因为它的错误message是支持EL表达式计算的,所以需要导入此包。

说明:EL是一个工具包,它并不仅仅是只能用于Web(即使你绝大部分情况下都是用于web的jsp里),可以用于任意地方哦(类比Spring的SpEL)

核心API分析

官方给它的定义为:This class is the entry point for Bean Validation.它作为校验的入口,有三种方式来启动它:

最简单方式:使用默认的ValidatorFactory factory = Validation.buildDefaultValidatorFactory();?虽然是默认的单也会有如下2种情况:

1. 若使用了xml配置了一个provider,那就会使用这个provider来提供Factory

2. 若没有xml或者xml力没有配置provider,那就是用默认的ValidationProviderResolver实现类来处理

  1. 方式二:选择自定义的ValidationProviderResolver来跟XML配置逻辑选出一个ValidationProvider来。大致代码如下:
Configuration configuration = Validation.byDefaultProvider()
        .providerResolver(new MyResolverStrategy()) // 自定义一个ValidationProviderResolver的实现类
        .configure();
ValidatorFactory factory = configuration.buildValidatorFactory();
  1. 第三种方式就更加自由了:你可以直接提供一个类型安全ValidationProvider实现。比如HibernateValidator就是一个ValidationProvider的实现:
  2. HibernateValidatorConfiguration configuration = Validation.byProvider(HibernateValidator.class)
            // .providerResolver( ... ) // 因为制定了Provider,这个参数就可选了
            .configure()
            .failFast(false);
    ValidatorFactory validatorFactory = configuration.buildValidatorFactory();

    这三种初始化方式,在源码处就是对应提供的三个public static方法:

  3. public class Validation {
    
        // 方式一
        public static ValidatorFactory buildDefaultValidatorFactory() {
            return byDefaultProvider().configure().buildValidatorFactory();
        }
        // 方式二
        public static GenericBootstrap byDefaultProvider() {
            return new GenericBootstrapImpl();
        }
        // 方式三
        public static <T extends Configuration<T>,U extends ValidationProvider<T>> ProviderSpecificBootstrap<T> byProvider(Class<U> providerType) {
            return new ProviderSpecificBootstrapImpl<>( providerType );
        }
        ...
    }

    对于若你想使用xml文件独立配置校验规则,可以使用Configuration.addMapping(new FileInputStream(validationFile));,现在很少这么使用,略~

    使用注意事项:ValidatorFactory被创建后应该缓存起来再提供使用,因为它是线程安全的。

    因为现在都会使用Hibernate-Validation来处理校验,因此此处只关心方式三~

  4. HibernateValidatorConfiguration

    此接口表示配置,继承自标注接口javax.validation.Configuration。很明显,它是HibernateValidator的专属配置类

  5. 先看顶级接口:javax.validation.Configuration,为构建ValidatorFactory的配置类。默认情况下,它会读取配置文件META-INF/validation.xml,Configuration提供的API方法是覆盖xml配置文件项的。若没有找到validation.xml,就会使用默认的ValidationProviderResolver也就是:DefaultValidationProviderResolver。

    public interface Configuration<T extends Configuration<T>> {
        // 该方法调用后就不会再去找META-INF/validation.xml了
        T ignoreXmlConfiguration();
        // 消息内插器  它是个狠角色,关于它的使用场景,后续会有详解(包括Spring都实现了它来做事)
        // 它的作用是:插入给定的约束冲突消息
        T messageInterpolator(MessageInterpolator interpolator);
        // 确定bean验证提供程序是否可以访问属性的协定。对每个正在验证或级联的属性调用此约定。(Spring木有实现它)
        // 对每个正在验证或级联的属性都会调用此约定
        // Traversable: 可移动的
        T traversableResolver(TraversableResolver resolver);
        // 创建ConstraintValidator的工厂
        // ConstraintValidator:定义逻辑以验证给定对象类型T的给定约束A。(A是个注解类型)
        T constraintValidatorFactory(ConstraintValidatorFactory constraintValidatorFactory);
        // ParameterNameProvider:提供Constructor/Method的方法名们
        T parameterNameProvider(ParameterNameProvider parameterNameProvider);
        // java.time.Clock 用作判定@Future和@Past(默认取值当前时间)
        // 若你希望他是个逻辑实现,提供一个它即可
        // @since 2.0
        T clockProvider(ClockProvider clockProvider);
        // 值提取器。这是add哦~ 负责从Optional、List等这种容器里提取值~
        // @since 2.0
        T addValueExtractor(ValueExtractor<?> extractor);
        // 加载xml文件
        T addMapping(InputStream stream);
        // 添加特定的属性给Provider用的。此属性等效于XML配置属性。
        // 此方法通常是框架自己分析xml文件得到属性值然后放进去,调用者一般不使用(当然也可以用)
        T addProperty(String name,String value);
        
        // 下面都是get方法喽
        MessageInterpolator getDefaultMessageInterpolator();
        TraversableResolver getDefaultTraversableResolver();
        ConstraintValidatorFactory getDefaultConstraintValidatorFactory();
        ParameterNameProvider getDefaultParameterNameProvider();
        ClockProvider getDefaultClockProvider();
        BootstrapConfiguration getBootstrapConfiguration(); // 整个配置也可返回出去
    
        // 上面都是工作,这个方法才是最终需要调用的:得到一个ValidatorFactory
        ValidatorFactory buildValidatorFactory();
    }

    该接口提供了一些标准的配置项。在实际应用中都是使用Hibernate Validation,所以再看看这个具体的子接口:

    public interface HibernateValidatorConfiguration extends Configuration<HibernateValidatorConfiguration> {
    
        // 这批属性,证明直接可以通过System属性值来控制,大大地方便~
        // 这个机制快速失败机制:true检查完一个有错误就返回,false全部检查完把错误消息一起返回   默认false
        String FAIL_FAST = "hibernate.validator.fail_fast"; 
        String ALLOW_PARAMETER_CONSTRAINT_OVERRIDE = "hibernate.validator.allow_parameter_constraint_override";
        String ALLOW_MULTIPLE_CASCADED_VALIDATION_ON_RESULT = "hibernate.validator.allow_multiple_cascaded_validation_on_result";
        String ALLOW_PARALLEL_METHODS_DEFINE_PARAMETER_CONSTRAINTS = "hibernate.validator.allow_parallel_method_parameter_constraint";
        // @since 5.2
        @Deprecated
        String CONSTRAINT_MAPPING_CONTRIBUTOR = "hibernate.validator.constraint_mapping_contributor";
        // @since 5.3
        String CONSTRAINT_MAPPING_CONTRIBUTORS = "hibernate.validator.constraint_mapping_contributors";
        // @since 6.0.3
        String ENABLE_TRAVERSABLE_RESOLVER_RESULT_CACHE = "hibernate.validator.enable_traversable_resolver_result_cache";
        // @since 6.0.3  ScriptEvaluatorFactory:执行脚本
        @Incubating
        String SCRIPT_EVALUATOR_FACTORY_CLASSNAME = "hibernate.validator.script_evaluator_factory";
        // @since 6.0.5 comparing date/time in temporal constraints. In milliseconds.
        @Incubating
        String TEMPORAL_VALIDATION_TOLERANCE = "hibernate.validator.temporal_validation_tolerance";
    
        // ResourceBundleMessageInterpolator用于 load resource bundles
        ResourceBundleLocator getDefaultResourceBundleLocator();
        // 创建一个ConstraintMapping:通过编程API配置的约束映射
        // 设置映射后,必须通过addMapping(constraintmapping)将其添加到此配置中。
        ConstraintMapping createConstraintMapping();
        // 拿到所有的值提取器  @since 6.0
        @Incubating
        Set<ValueExtractor<?>> getDefaultValueExtractors();
    
        // 往下就开始配置了~~~~~~~~~~
        HibernateValidatorConfiguration addMapping(ConstraintMapping mapping);
        HibernateValidatorConfiguration failFast(boolean failFast);
        // used for loading user-provided resources:
        HibernateValidatorConfiguration externalClassLoader(ClassLoader externalClassLoader);
        // true:表示允许覆盖约束的方法。false表示不予许(抛出异常)  默认值是false
        HibernateValidatorConfiguration allowOverridingMethodAlterParameterConstraint(boolean allow);
        // 定义是否允许对返回值标记多个约束以进行级联验证。  默认是false
        HibernateValidatorConfiguration allowMultipleCascadedValidationOnReturnValues(boolean allow);
        // 定义约束的**并行方法**是否应引发ConstraintDefinitionException
        HibernateValidatorConfiguration allowParallelMethodsDefineParameterConstraints(boolean allow);
        // 是否允许缓存TraversableResolver  默认值是true
        HibernateValidatorConfiguration enableTraversableResolverResultCache(boolean enabled);
        // 设置一个脚本执行器
        @Incubating
        HibernateValidatorConfiguration scriptEvaluatorFactory(ScriptEvaluatorFactory scriptEvaluatorFactory);
        // 允许在时间约束中比较日期/时间时设置可接受的误差范围
        // 比如@Past @PastOrPresent @Future @FutureOrPresent
        @Incubating
        HibernateValidatorConfiguration temporalValidationTolerance(Duration temporalValidationTolerance);
        // 允许设置将传递给约束验证器的有效负载。如果多次调用该方法,则只传播最后传递的有效负载。
        @Incubating
        HibernateValidatorConfiguration constraintValidatorPayload(Object constraintValidatorPayload);
    }

    关于此接口的唯一实现类:ConfigurationImpl,这里就不用再做分析了,因为对于Validation这块,咱们面向接口编程是完全没有问题的~

    准备好了Configuration后,下一步显然就是configuration.buildValidatorFactory()来得到一个ValidatorFactory喽,关于ValidatorFactory这块的内容,请听下文分解~

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读