valang 由一系列的规则组成,对于每个规则,它的结构如下:
{ <key> : <predicate_expression> : <message> [: <error_code> [: <args> ]] } |
其中:
- <key>,属性表达式。它是要验证的属性名,必填。
- <predicate_expression>,谓词表达式。期望满足的条件,必填。
- <message>,缺省消息。当不满足预期表达式时显示的消息,必填。
- <error_code>,错误码。国际化时使用的消息键值,当它存在时缺省消息被忽略,可选。
- <args>,参数。逗号分隔的参数值,用于替换错误码中定义的站位符,可选。
这些参数,除了谓词表达式之外,都可以在 Spring 中见到它们的踪影。而它们的含义、用法也丝毫没有发生变化。因此,让我们把关注的焦点集中在谓词表达式上。
valang 将谓词表达式定义成如下结构:
<expression> ::= <expression> ( ( "AND" | "OR" ) <expression> )+ | <predicate> |
其中的谓词(<predicate>),是由操作符、文字常量、bean 属性、数学表达式和函数组成的可计算实体。
1.操作符
valang 的操作符有三种:一元操作符,二元操作符和其他操作符。支持的操作符如下表:
类型 | 支持类型 | 操作符 | 说明 |
一元 | 对象 | NULL | IS NULL | 为空 |
NOT NULL | IS NOT NULL | 非空 | ||
字符串 | HAS TEXT | 包含非空格的字符 | |
HAS NO TEXT | null或只有空格字符 | ||
HAS LENGTH | IS NOT BLANK | 长度>0 | ||
HAS NO LENGTH | IS BLANK | null或长度=0 | ||
IS UPPERCASE | IS UPPER CASE | IS UPPER | 所有字母大写 | ||
IS NOT UPPERCASE | IS NOT UPPER CASE | IS NOT UPPER | 不是所有字母大写 | ||
IS LOWERCASE | IS LOWER CASE | IS LOWER | 所有字母小写 | ||
IS NOT LOWERCASE | IS NOT LOWER CASE | IS NOT LOWER | 不是所有字母小写 | ||
IS WORD | |||
IS NOT WORD | |||
二元 | 字符串、日期、布尔和数字 | = | == | IS | EQUALS | 相等 |
!= | <> | >< | IS NOT | NOT EQUALS | 不等 | ||
数字、日期 | >|GREATER THAN | IS GREATER THAN | 大于 | |
< | LESS THAN | IS LESS THAN | 小于 | ||
>= | => | GREATER THAN OR EQUALS | IS GREATER THAN OR EQUALS | 大于等于 | ||
<= | =< | LESS THAN OR EQUALS | IS LESS THAN OR EQUALS | 小于等于 | ||
其他 | 数字、日期 | BETWEEN A AND B | 大于等于A,且小于等于B |
NOT BETWEEN A AND B | 小于A,或大于B | ||
字符串、日期、布尔和数字 | IN <value> (, <value>)* | 在值列表中 | |
NOT IN <value> (, <value>)* | 不在值列表中 | ||
布尔表达式 | NOT | 取反 |
这些操作符是大小写非敏感的,另外在使用时需要注意:
- 避免在验证时出现 NullException,这一点可以在 userValidator 的例子中看到。
- 二元操作符两边的操作数类型必须一致。
2.文字常数
Valang 支持 4 种文字常数:字符串、数字、日期和布尔。这四种类型的文字常数如下表:
类型 | 说明 |
---|---|
字符串 | 字符串使用单引号包含,如 ’foxgem’。 |
数字 | 数字被转换为 java.math.BigDecimal。 |
布尔 | 布尔值必须是:TRUE、YES、FALSE、NO 中的一个,且不要使用单引号。 |
日期 | 日期值使用 [] 包含,如 [2000-01-01]。 |
alang 对于日期类型的处理非常特殊,而且有趣:
- 使用 T 来代表当前时间, 如 [T]。
-
提供了移位操作,语法是:日期 > 时间单位,日期 < 时间单位。前者表示将日期中对应的单位部分向后滚动到该时间单位的最大值;后者表示将日期中对应的单位部分向前滚动到该时间单位的最小值。支持的时间单位和对应范围:
1) 秒(s):0~999 毫秒
2) 分钟(m):0秒0毫秒 ~ 59秒999毫秒
3) 小时(H):0分0秒0毫秒 ~ 59分59秒999毫秒
4) 天(d):0时0分0秒0毫秒 ~ 23时59分59秒999毫秒
5) 星期(w):周一0时0分0秒0毫秒 ~ 周日23时59分59秒999毫秒
6) 月(M):月第一天0时0分0秒0毫秒 ~ 月最后一天23时59分59秒999毫秒
7) 年(y):年第一天0时0分0秒0毫秒 ~ 年最后一天23时59分59秒999毫秒如:[T>d],表示当天的 23 时 59 分 59 秒 999 毫秒。
-
支持日期的增减运算。增减分别对应:“+”和“-”;数量对应:i 时间单位,i 表示整数。如:[T>d-1d+2H],表示先将当前时间后滚至当天的最后时刻,再减去一天,再加上 2 个小时。
日期格式在 valang 中也有很好的支持,可以很方便的定义自己的格式。日期解析过程如下:
1) 取出日期字符串,在 dateParsers 中定义 Map 的正则表达式键集合中进行匹配。
2) 当找到符合的正则表达式时,以它为键值获取对应的日期格式串。
3) 使用 SimpleDateFomat,将日期字符串按照日期格式串转化为对应的 Date 类型。因此,如果要在 valang 中使用 yyyy-MM-dd 的格式时,那么使用以下的定义:
-
<entry key="^\\d{4}-\\d{2}-\\d{2}$" value="yyyy-MM-dd" />
3. bean 属性
bean 属性的用法和属性表达式的用法一样,在运行时它的值会被取出。另外,valang 使用“?”来表示在规则中定义的属性表达式的值。
4. 数学表达式
valang 支持的数学运算符有:+、-、*、/(或 div)、%(或 mod)。
5.函数
valang 内置的函数如下:
函数 | 说明 |
length | len | size |count | 如果传入参数是集合或数组,那么返回它们的大小;否则,返回传入参数的 toString 所返回字符串的长度。 使用例子:length(?)<50 |
match | matches | 传入参数(参数 2)的 toString 返回的字符串是否匹配给定的正则表达式(参数1)。 使用例子:match('^\\d{2}$', ?) is true |
传入参数的 toString 返回的字符串是有效的 email。 使用例子:email(?) is true | |
upper | 将传入参数的 toString 返回的字符串转换为大写。 使用例子:upper(?)=='VALANG' |
lower | 将传入参数的 toString 返回的字符串转换为小写。 使用例子:lower(?)=='valang' |
! | 布尔值取反 使用例子:!(?) is true。 注意:它不能内嵌表达式,如“!(length(?)<= 50) is true”将会出错。 |
resolve | 解析消息码。 |
inRole | 传入参数是角色名,如果当前用户有这个角色,则返回 true。 |
valang 对于函数的支持并不是只仅仅提供几个简单的函数,它还允许用户自定义函数。这大大的扩展了它的表现力。
org.springmodules.validation.valang.functions.AbstractFunction 是 valang 函数的基类,所有的内置函数都是它的子类,自定义函数也必须从它继承。AbstractFunction 有几个重要的方法需要注意:
- 构造函数,子类必须实现,不要忘了先调用 super。
- definedExactNumberOfArguments、definedMaxNumberOfArguments、 definedMinNumberOfArguments,分别是定义函数的参数个数,函数的最大参数个数、函数的最小参数个数。它们在构造函数中被调 用。
- init,在所有属性被设置后调用,负责初始化。
- isAutowireByName 和 isAutowireByType,分别指定了函数的属性被 BeanFactory 自动装配采用的方法。
- doGetResult,返回函数的结果。
下面通过实现一个自定义版本的 length 函数来了解自定义函数的编写和配置:
函数定义:
public class AnotherLengthFunction extends AbstractFunction { public AnotherLengthFunction(Function[] functions, int line, int column) { super(functions, line, column); //AnotherLengthFunction只支持一个参数 definedExactNumberOfArguments(1); } protected Object doGetResult(Object target) throws Exception { //返回参数1的toString的值。如果有多个参数,那么: //参数2:getArguments()[1].getResult(target).toString();以此类推。 //另外getArguments()的个数就是实际传入的参数个数。 String value= getArguments()[0].getResult(target).toString(); return new Integer(value.length()); } } |
函数配置:
<property name="customFunctions"> <map> <!-- 前者是函数使用的名字,后者对应函数实现的class。 使用例子:anotherLength(?)<50 --> <entry key="anotherLength" value="AnotherLengthFunction"/> </map> </property> |
当自定义的函数需要外部的资源,如数据库连接、网络连接、文件等,可以通过 BeanFactory 自动装配来完成。此时需要覆盖 isAutowireByName 或 isAutowireByType,指定自动装配的类型,这两个函数不能同时覆盖。
对于 Web 应用程序来说,服务器的验证固然是必须的,但如果提供了客户端验证的话,那无疑是锦上添花的事情。幸运的是,Valang Validator 已经做到了这一点。要使用这一功能,需要先了解 2 个组件:
1. org.springmodules.validation.valang.javascript.taglib.ValangRulesExportInterceptor
这个拦截器的作用是输出被当前请求处理器所使用的 valang 验证规则到 ModelAndView 中,使 <validate> 可以利用这些信息。
在以下情况下之一时,该拦截器不做任何事情:
- 当被拦截的请求处理器不是 BaseCommandController。
- 请求处理器使用的 Validator 不是 ValangValidator 实例。
- 请求处理器不输出一个 Command 对象到 model 中。
2. valang 标签库
valang 标签库完成客户端脚本的产生,并进行实际的客户端验证。它包含了 2 个标签:
标签 | 作用 | 属性 | 说明 |
codebase | 产生被翻译的 javascript 验证规则执行所需要的代码。它应该每页包含一次,且在被产生的验证规则之前。 | includeScriptTags | 非必须,指示产生的 javascript 代码是否被 <script> 包含。 |
fieldErrorsIdSuffix | 非必须,指明用来包含属性错误信息的 <div> 或 <span> 的 id 的后缀。整个 id 的最后组成: <属性名 >< fieldErrorsIdSuffix > | ||
globalErrorsId | 非必须,指明用来包含全局错误信息的 <div> 或 <span> 的 id。 | ||
validate | 将valang规则翻译成对应的 javascript 验证规则。 | commandName | 非必须,指明需要验证的 command 对象名字。 |