小细节:
可以看到,导入了hibernate-validator
就不必要再自己导入Java Bean Validation
API了,因此建议不用再手动导入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.它作为校验的入口,有三种方式来启动它:
- 最简单方式:使用默认的ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); 虽然是默认的单也会有如下2种情况:1. 若使用了xml配置了一个provider,那就会使用这个provider来提供Factory2. 若没有xml或者xml力没有配置provider,那就是用默认的ValidationProviderResolver实现类来处理
- 方式二:选择自定义的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的专属配置类
先看顶级接口: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(); }