0. 背景介绍
输入验证pring 处理的最重要 Web 开发任务之一,在 Spring MVC中有两种方式可以验证输入:一种是Spring 自带的验证框架,另外一种是利用JSR实现,JSR验证比Spring⾃带的验证器使用起来⽅便很多。JSR 是一个规范文档,指定了一整套API,通过标注给对象属性添加约束。Hibernate Validator 就是 JSR 规范的具体实现, Hibernate Validator 提供了 JSR 规范中所有内置约束注解的实现,以及一些附加的约束注解,除此之外用户还可以自定义约束注解。
然而 Hibernate Validator 提供的接口没有直接支持输出本地化的错误验证消息。本文结合项目实践,总结了如何对内置的和⽤户⾃定义的约束注解提供本地化支持,以及如何从用户自定义的资源文件中读取本地化的错误 验证消息。但现在play框架下不支持 Hibernate Validator的国际化,因此需要对play框架下的 Hibernate Validator进⾏重构。
1. Hibernate Validator 概述
在Java应⽤程序中,必须要对输入进来的数据从语义上分析是有效的,也就是数据校验。对数据的校验是一项 贯穿于从表示层到持久化层的常见任务,通常在每个层中都需要做相同的验证逻辑,然而不同的层通常有不同的开发人员做编码,这样就会存在冗余代码以及语义一致性等代码管理上的问题。
为了避免重复验证以及管理问题,开发⼈员经常将验证逻辑与相应的域模型进行捆绑。JSR 349 Bean 验证 1.1 是一个数据验证的规范,为 Java Bean 验证定义了相应的元数据模型和接口,默认的元数据是 Java 注释,通过使用 XML 对原有的元数据进行覆盖和扩展。在 Java 应⽤程序中,通过使⽤ Bean 验证自带的约束或者⽤户自定义的约束验证来确保 Java Bean 的正确性。Bean 验证是一个 runtime 的数据验证框架,如果验证失败, 错误信息会⽴马返回。从而使验证逻辑从业务代码中分离出来。
Hibernate Validator 是 对 JSR 349 验证规范的具体实现,提供了了 JSR 规范中所有内置约束注解的实现,以及 ⼀一些附加的约束注解。
2. Hibernate Validator 对全球化⽀支持概述
世界经济日益全球化的同时软件国际化势在必然,当一个软件或者应用需要在全球范围内使用的时候,最简单的要求就是界⾯上的信息能用本地化的语言来显示。然而 Hibernate Validator 4.0 提供的接⼝对全球化支持存在下面两个问题:
问题 1:
只能显示英文的错误消息,不能读取翻译的错误验证消息。因为默认的消息解释器(message interpolator)使用的是 JVM 默认的 locale(Locale.getDafult()),通常情况下为英文;
问题 2:
Hibernate Validator 验证过程中的失败消息默认是从类路径下的资源文件 ValidationMessage.properties 读 取,然而在实际项⽬中,通常会根据模块结构⾃定义资源文件名称,⽅便源代码的管理以及资源文件的重用。
3. ⾃自定义约束注解提供本地化⽀支持
⾃自定义注解
自定义约束注解就是用户根据⾃己的需要重新定义一个新的约束注解,通常包括两部分,⼀是约束注解的定义,二是约束验证器。
约束注解
约束注解就是对某一⽅法、字段、属性或其组合形式等进行约束的注解,通常包含以下几个部分:
4. Hibernate Validator校验⽅方式在Play框架下的尝试规律律总结
(1)⼤前提:
@DecimalMax(MAX_BIT_COIN_QUANTITY,inclusive=)设定边界值String MAX_BIT_COIN_QUANTITY = "21000000";
Hibernate Validator校验⽅式默认从 ValidationMessages.properties:javax.validation.constraints.DecimalMax.message = must be less than ${inclusive == true ? 'or equal to ' : ''}{value}中获值
情况一:inclusive默认为true,输入:quantity:"21000001"
输出:
情况⼆:inclusive默认为true,输入:quantity:"21000000"
输出:
情况三:inclusive为false,输入:quantity:"21000001"
输出:
情况四:inclusive为false,输入:quantity:"21000000"
输出:
结论:⽆论请求头的语言变为什么,都是输出中文,因为是从本地的JVM中拿的语言。
(2)⼤前提:message.en⽂件内容:error.decimal.max=must be less than {0}
情况一:inclusive默认为true,输⼊:quantity:"21000001"
输出:
情况二:inclusive默认为true,输入:quantity:"21000000"
输出:
情况三:inclusive为false,输入:quantity:"21000001"
输出:
情况四:inclusive为false,输⼊:quantity:"21000000"
输出:
结论:若inclusive为true时,最大值校验包含边界值,大于边界值时会在错误信息中返true; 若inclusive 为false时,最大值校验不包含边界值,⼤于或等于边界值时会在错误信息中返false。
(3)⼤前提:message.en文件内容:error.decimal.max=must be less than {1}
情况一:inclusive默认为true,输入:quantity:"21000001"
输出:
情况二:inclusive默认为true,输入:quantity:"21000000"
输出:
情况三:inclusive为false,输入:quantity:"21000001"
输出:
情况四:inclusive为false,输入:quantity:"21000000"
输出:
结论: 若inclusive为true时,最大值校验包含边界值,⼤于边界值时会在错误信息中返边界值 若inclusive 为false时,最大值校验不包含边界值,大于或等于边界值时会在错误信息中返边界值
5. Hibernate Validator ⼯作原理
即此时使用的hibernate实现,注⼊了spring的messageSource来解析消息时:
即内部委托给了
org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator#ResourceBundleMessageIn terpolator,并使用如下代码解析消息:
此处可以看到context.getConstraintDescriptor().getAttributes(),其作用是获取到注解如@Length上的所有数据,具体代码实现如下:
循环每⼀个方法 并获取值放入map,接着进入方法:
6. Hibernate Validator 使用用户⾃定义的资源文件
文章开头提到 Hibernate Validator 验证过程中第二个问题是验证失败的错误消息默认从类路径下的ValidationMessage.properties 中读取,但是很多时候我们希望从自定义的资源文件(resource bundle)文件中读取,而不是指定路径下的ValidationMessage.properties,以便于翻译以及重用已有的资源文件。在这种情况下,Hibernate Validator 的资源包定位器ResourceBundleLocator 可以解决这个问题。
Hibernate Validator 中的默认资源文件解析(ResourceBundleMessageInterpolator)将解析资源文件和检索相应的错误消息委派给资源包定位器 ResourceBundleMessageInterpolator。如果要使用用户⾃定义的资源⽂件,只需要将用户⾃定义资源文件名作为参数传递给资源包定位器 PlatformResourceBundleLocator,在启用 ValidatorFactory 的时候将新的资源包定位器示例作为参数传递给 ValidatorFactory。
7. 适⽤用于Play框架下注解的国际化改造
@DecimalMax
操作参数简介
修饰符和类型 | 操作参数 | 描述 |
String | value(必须 参数) | 根据BigDecimal的最大值用String类型的字符串表示,要求值必须⼩于或等于最大限制值 |
Class<?>[] | groups | default为{} |
boolean | inclusive | 指定是否包含最大值,值必须小于等于(inclusive=true)/小于 (inclusive=false)value属性指定的值。 |
String | message | default为 "{javax.validation.constraints.DecimalMax.message}" |
Class<? extends Payload>[] | payload | default为{} |
注:String message() default "{javax.validation.constraints.DecimalMax.message}"会从指定路径下 ValidationMessage.properties拿⽂件,默认的消息解释器(message interpolator)使⽤的是 JVM 默认的 locale(Locale.getDafult())会从本地JVM中拿语言,而不能随Play框架下的语言的改变⽽改变,故必须重构Hibernate Validator 验证方式注解中的message。
实现步骤
第一步:添加一个messge的key值
第二步:在对应语言版本的message⽂文件中添加key对应的value值
注:{1}⽽不是{0},{0}默认inclusive为true,{1}value属性指定的值。因为原⽣的文件有这样一个限制: javax.validation.constraints.DecimalMax.message = must be less than ${inclusive == true ? 'or equal to ' : ''}{value}
inclusive用法
@DecimalMax(value=,inclusive=) | 值必须小于等于(inclusive=true)/小于(inclusive=false)value属性指定的值。
例1:
或
注意:表示输入最⼤的数量必须小于或等于21000000
例2:
注:表示输⼊最大的数量必须小于21000000,但不包括21000000! Hibernate Validator常⽤参数校验注解
Hibernate Validator常⽤参数校验注解
Hibernate Validator校验注解 | 说明 |
@NotNull | 值不能为空 |
@Null | 值必须为空 |
@Pattern(regex=) | 字符串必须匹配正则表达式 |
@Size(min=, max=) | 集合的元素数量必须在min和max之间 |
@CreditCardNumber(ignoreNonDigitCharacters=) | 字符串必须是信用卡号(按美国的标准验的-_-!) |
字符串必须是Email地址 | |
@Length(min=,max=) | 检查字符的长度 |
@NotBlank | 字符串必须有字符 |
@NotEmpty | 字符串不为null,集合有元素 |
@Range(min=,max=) | 数字必须大于等于min,⼩于等于max |
@SafeHtml | 字符串是安全的html |
@URL | 字符串是合法的URL |
@AssertFalse | 值必须是false |
@AssertTrue | 值必须是true |
@DecimalMax(value=,inclusive=) | 值必须小于等于(inclusive=true)/⼩于 (inclusive=false)value属性指定的值。可注解在字符串类型的属性上 |
@DecimalMin() | 值必须大于等于(inclusive=true)/大于 (inclusive=false)value属性指定的值。可注解在字符串类型的属性上 |
@Digits(integer=,fraction=) | 数字格式检查,integer指定整数部分的最大⻓长度, fraction指定小数部分的最⼤长度 |
@Future | |
@Past | |
@Max(value=) | 值必须小于等于value指定的值,不能注解在字符串类型的属性上 |
@Min(value=) | 值必须大于等于value指定的值,不能注解在字符串类型的属性上 |
| |