深入了解数据校验:Java Bean Validation 2.0(JSR303、JSR349、JSR380)Hibernate-Validation 6.x使用案例【享学Java】(中)

简介: 深入了解数据校验:Java Bean Validation 2.0(JSR303、JSR349、JSR380)Hibernate-Validation 6.x使用案例【享学Java】(中)

小细节:


image.png

可以看到,导入了hibernate-validator就不必要再自己导入Java Bean ValidationAPI了,因此建议不用再手动导入API,交给内部来管理依赖。

定义一个待校验的普通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("fsx@gmail.com", "baidu@baidu.com", "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)


Expression Language 3.0表达式语言规范最终版于2013-4-29发布的,Tomcat 8、Jetty 9、GlasshFish 4都已经支持了EL 3.0。新特性包括:符串拼接操作符、符串拼接操作符、赋值、分号操作符、对象方法调用、Lambda表达式、静态字段/方法调用、构造器调用、Java8集合操作


那行,导入依赖javax.el以及它的实现:

<!-- 注意这里导入的是Apr, 2013发布的el3.x的版本,但是glassfish-web并没有对此版本进行支持了  当然tomcat肯定是支持的 -->
<dependency>
    <groupId>javax.el</groupId>
    <artifactId>javax.el-api</artifactId>
    <version>3.0.1-b06</version>
</dependency>
<!-- servlet容器大都对el有实现(支持jsp的都对此有实现),比如tomcat/glassfish等 -->
<dependency>
    <groupId>org.glassfish.web</groupId>
    <artifactId>javax.el</artifactId>
    <version>2.2.6</version>
</dependency>


需要注意的是,网上大都建议导入org.glassfish.web包。但是EL3.0后它并没有再提供支持了,因此我个人是不建议使用它,而是使用下面tomcat的实现的~

当然org.glassfish.web没支持了,你可以知道导入org.glassfish,它的GAV如下:


<!-- https://mvnrepository.com/artifact/org.glassfish/javax.el -->
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>javax.el</artifactId>
    <version>3.0.1-b11</version>
</dependency>


关于EL的实现此处啰嗦一句:JavaEE并没有提供el的实现,需要容器自行提供,比如上面你想要导入最为流行的tomcat,你可以导入如下jar即可:


<!-- 嵌入式的tomcat -->
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-el</artifactId>
    <version>9.0.22</version>
</dependency>
<!-- 传统的tomcat(需要注意的是:传统的tomcat这种jar是不需要你手动导入的,tomcat自带的) -->
<dependency>
    <groupId>org.apache.tomcat</groupId>
    <artifactId>tomcat-jasper-el</artifactId>
    <version>9.0.22</version>
    <scope>provided</scope>
</dependency>

此处还需要说明一点的是:嵌入式tomcat(比如SpringBoot环境)若要使用时需要显示导入的。但是传统tomcat中你若要使用是不用自己导入的(tomcat自带此jar)。


但是,但是,但是自从tomcat8.5后不再自带jsper-el的包了,需要手动导入。(tomcat7还是有的~)


最佳实践:


一般来说,javax.el-api以及validation-api都是没有必要单独导入的,第三方包都会自带。所以绝大数情况下,我们只需要这么导入即可正常work,形如下面这样非常赶紧整洁:


<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.17.Final</version>
</dependency>
<dependency>
    <groupId>org.apache.tomcat.embed</groupId>
    <artifactId>tomcat-embed-el</artifactId>
    <version>9.0.22</version>
</dependency>


此处可能有伙伴会问:为何自己在使用的时候从来都没有导入过EL相关Jar包,也能正常数据校验呢?


答:那是因为绝大多数情况下你使用@Valid是使用在Spring MVC上,它是不依赖于EL方式的,下篇文章会详细说明关于数据校验在Spring上的使用。而本文主要还是讲解API的方式~


经过一番导包后,再次运行打印如下(方式一、方式二结果一致):


name名字不能为null: null //  此处错误消息是自己的自定义内容
age必须是正数: -1
emails[2].<list element>不是一个合法的电子邮件地址: aaa.com

这样通过API调用的方式就完成了对这个JavaBean的属性校验~


核心API分析


Validation


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


  1. 最简单方式:使用默认的ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); 虽然是默认的单也会有如下2种情况:1. 若使用了xml配置了一个provider,那就会使用这个provider来提供Factory2. 若没有xml或者xml力没有配置provider,那就是用默认的ValidationProviderResolver实现类来处理
  2. 方式二:选择自定义的ValidationProviderResolver来跟XML配置逻辑选出一个ValidationProvider来。大致代码如下:


Configuration configuration = Validation.byDefaultProvider()
        .providerResolver(new MyResolverStrategy()) // 自定义一个ValidationProviderResolver的实现类
        .configure();
ValidatorFactory factory = configuration.buildValidatorFactory();

'

3.第三种方式就更加自由了:你可以直接提供一个类型安全的ValidationProvider实现。比如HibernateValidator就是一个ValidationProvider的实现


HibernateValidatorConfiguration configuration = Validation.byProvider(HibernateValidator.class)
        // .providerResolver( ... ) // 因为制定了Provider,这个参数就可选了
        .configure()
        .failFast(false);
ValidatorFactory validatorFactory = configuration.buildValidatorFactory();


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

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来处理校验,因此此处只关心方式三~


HibernateValidatorConfiguration


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

image.png


先看顶级接口: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();
}
相关文章
|
8天前
|
SQL Java 数据库连接
从理论到实践:Hibernate与JPA在Java项目中的实际应用
本文介绍了Java持久层框架Hibernate和JPA的基本概念及其在具体项目中的应用。通过一个在线书店系统的实例,展示了如何使用@Entity注解定义实体类、通过Spring Data JPA定义仓库接口、在服务层调用方法进行数据库操作,以及使用JPQL编写自定义查询和管理事务。这些技术不仅简化了数据库操作,还显著提升了开发效率。
20 3
|
14天前
|
jenkins Java 测试技术
如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例详细说明
本文介绍了如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例,详细说明了从 Jenkins 安装配置到自动构建、测试和部署的全流程。文中还提供了一个 Jenkinsfile 示例,并分享了实践经验,强调了版本控制、自动化测试等关键点的重要性。
47 3
|
16天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
33 2
|
17天前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
12 2
|
17天前
|
缓存 Java 数据库连接
Hibernate:Java持久层框架的高效应用
通过上述步骤,可以在Java项目中高效应用Hibernate框架,实现对关系数据库的透明持久化管理。Hibernate提供的强大功能和灵活配置,使得开发者能够专注于业务逻辑的实现,而不必过多关注底层数据库操作。
11 1
|
22天前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
15 1
|
1月前
|
Java 数据库
案例一:去掉数据库某列中的所有英文,利用java正则表达式去做,核心:去掉字符串中的英文
这篇文章介绍了如何使用Java正则表达式从数据库某列中去除所有英文字符。
46 15
|
1月前
|
jenkins Java 测试技术
如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例详细说明
【10月更文挑战第8天】本文介绍了如何使用 Jenkins 自动发布 Java 代码,通过一个电商公司后端服务的实际案例,详细说明了从 Jenkins 安装配置到自动构建、测试和部署的全流程。文中还提供了一个 Jenkinsfile 示例,并分享了实践经验,强调了版本控制、自动化测试等关键点的重要性。
34 5
|
1月前
|
分布式计算 NoSQL Java
Hadoop-32 ZooKeeper 分布式锁问题 分布式锁Java实现 附带案例和实现思路代码
Hadoop-32 ZooKeeper 分布式锁问题 分布式锁Java实现 附带案例和实现思路代码
43 2
|
1月前
|
Java C#
Java的监听处理事件--小球移动案例
Java的监听处理事件--小球移动案例
13 0