3.5 使用验证器进行输入校验
类型转换和输入校验是两个紧密相关、先后处理的问题,以一个用户输入自己的年龄为例,Web应用收集到的年龄参数总是String类型的,因此Web应用需要先将它转换为整数类型的值;转换为整数类型的值之后,应用还需要检查该值是否为有效的年龄值。假如用户为年龄输入“abc”,那么类型转换就会失败;如果用户输入“1000”,它可以通过类型转换,但在输入校验阶段会失败。
3.5.1 输入校验概述
输入校验也是所有Web应用必须处理的问题,因为Web应用的开放性,网络上所有的浏览者都可以自由使用该应用,因此该应用通过输入页面收集的数据是非常复杂的,不仅会包含正常用户的误输入,还可能包含恶意用户的恶意输入。一个健壮的应用系统必须将这些非法输入阻止在应用之外,防止这些非法输入进入系统,这样才可以保证系统不受影响。
异常输入,轻则导致系统非正常中断,重则导致系统崩溃。应用程序必须能正常处理表现层接收的各种数据,通常的做法是遇到异常输入时应用程序直接返回,提示浏览者必须重新输入,也就是将那些异常输入过滤掉。对异常输入的过滤,就是输入校验,也称为数据校验。
输入校验分为客户端校验和服务器端校验,客户端校验主要是过滤正常用户的误操作,主要通过JavaScript代码完成;服务器端校验是整个应用阻止非法数据的最后防线,主要通过在应用中编程实现。
提示
客户端校验的主要作用是防止正常浏览者的误输入,仅能对输入进行初步过滤;对于恶意用户的恶意行为,客户端校验将无能为力。因此,客户端校验绝不可代替服务器端校验。当然,客户端校验也绝不可少,因为Web应用大部分浏览者都是正常的浏览者,他们的输入可能包含了大量的误输入,客户端校验把这些误输入阻止在客户端,从而降低了服务器的负载。
JSF框架提供了强大的类型转换机制,也提供了强大的输入校验功能,JSF的输入校验主要是服务器端校验支持。不过借助于一些第三方的开源JSF实现(例如JSF-COMP等),它们也可以提供客户端校验支持。
与JSF类型转换相同的是,JSF提供了大量内建的校验器,这些内建校验器可以完成大部分通用的输入校验;除此之外,JSF也允许开发者开发更复杂的自定义校验器。JSF的自定义校验支持两种实现方式:
在托管Bean中提供一个可实现校验的方法。
通过实现Validator接口来实现自定义校验器。
如果采用第一种方式来实现自定义校验,JSF页面中使用起来比较简单,只要通过UI组件的validator属性对该校验方法执行绑定即可;如果采用第二种方式来实现自定义校验,则还需要完成如下工作:
在faces-config.xml文件中注册校验器。
在JSF页面中通过<f:validator…/>标签或用户自定义标签来显式引用该校验器。
本章3.6节将会介绍自定义校验器的相关知识,这里先介绍JSF内置校验器的相关内容。
3.5.2 JSF内置校验器
JSF内置提供了一些基本的输入校验器,这些输入校验器可以完成大部分通用的校验工作。为了让开发者在页面中使用这些校验器,JSF为这些校验器提供对应的标签。JSF内置支持的校验器及其标签如表3.2所示。
表3.2 JSF内置校验器
上面3个基本校验器使用起来非常简单,将它们对应的标签放入需要校验的UI组件之内即可。使用这3个标签时可以指定如下3个属性:
maximum:指定浮点数、整数、字符串长度的最大值。
minimum:指定浮点数、整数、字符串长度的最大值。
binding:这个属性用于将输入校验组件本身绑定到托管Bean的属性。
JSF允许通过3种方式在JSF页面上进行输入校验,这3种方式包括:
通过专用标签引用指定输入校验器。例如,JSF内置的输入校验器都有对应的专用标签。
通过UI组件的validator属性引用托管Bean的校验方法进行校验。
通过<f:validator…/>标签的validatorId属性引用一个已注册的输入校验器进行校验。
对于JSF内置的输入校验器而言,通常采用第一种方式来引用校验器。下面示例模拟了一个网站备案的页面,当用户添加网站时需要输入3个字段:域名、运行时间和投入费用。
域名:该字段的字符串长度必须在5~25之间,该字段可用LengthValidator进行校验。
运行时间:该字段(整数类型)的值必须在0~30之间,该字段可用LongRangeValidator进行校验。
投入费用:该字段(浮点类型)的值必须在0~20之间,该字段可用DoubleRangeValidator进行校验。
下面是添加网站的输入页面代码,该页面中使用JSF内置校验器来对这3个字段进行输入校验。
程序清单:codes\03\3.5\validator\add.jsp
<h1>添加备案</h1> <h:form prependId="false"> 网站域名:<h:inputText value="#{registBean.name}"> <f:validateLength minimum="5" maximum="25"/> </h:inputText><br/> 运行时间(年):<h:inputText value="#{registBean.duration}"> <f:validateLongRange minimum="0" maximum="30"/> </h:inputText><br/> 投入费用(千元/年):<h:inputText value="#{registBean.cost}"> <f:validateDoubleRange minimum="0" maximum="20"/> </h:inputText><br/> <h:commandButton value="添加备案" action="#{registBean.add}"/> </h:form>
上面页面代码中粗体字代码指定了对3个输入组件的值进行输入校验,如果用户在该页面中输入的值不符合校验规则,则无法提交该页面。
与类型转换模型相同的是,当输入校验失败后虽然无法提交该页面,但页面上不会有任何提示。为了在页面上显示输入校验失败的错误提示,同样需要使用<h:messages…/>标签或<h:message…/>标签。下面进一步介绍如何显示校验失败的错误提示。
3.5.3 校验失败后的错误消息
为了在上面的add.jsp页面中显示校验失败的错误提示,必须在页面上增加<h:message…/>标签。例如,将上面add.jsp页面的代码改为如下形式。
程序清单:codes\03\3.5\validatorMsg\add.jsp
<h1>添加备案</h1> <h:form prependId="false"> 网站域名:<h:inputText value="#{registBean.name}" id="name"> <f:validateLength minimum="5" maximum="25"/> </h:inputText> <h:message for="name" style="color:red"/><br/> 运行时间(年):<h:inputText value="#{registBean.duration}" id="duration"> <f:validateLongRange minimum="0" maximum="30"/> </h:inputText> <h:message for="duration" style="color:red"/><br/> 投入费用(千元/年):<h:inputText value="#{registBean.cost}" id="cost"> <f:validateDoubleRange minimum="0" maximum="20"/> </h:inputText> <h:message for="cost" style="color:red"/><br/> <h:commandButton value="添加备案" action="#{registBean.add}"/> </h:form>
上面页面代码中3行粗体字代码用于输出3个字段输入校验失败后的错误提示消息。从上面代码可以看出,不管是类型转换失败的错误提示消息,还是输入校验失败的错误提示消息,都可使用<h:messages…/>或<h:message…/>标签来输出它们。
如果用户访问该页面时输入的值不能通过输入校验,则会看到如图3.26所示的页面。
图3.26 输入校验失败的错误提示
与类型转换失败相似的是,JSF为3个输入校验器提供了默认的错误提示消息,如果我们不想使用这些错误消息,同样可以通过提供自己的资源文件来覆盖这些错误消息。关于JSF为输入校验器提供的默认错误提示消息可以参考3.3节介绍。
例如,我们提供如下国际化资源文件。
程序清单:codes\03\3.5\CustomMsg\WEB-INF\src\crazyitMessages.properties
javax.faces.validator.LengthValidator.MAXIMUM={1}: 校验失败: 字符串长度不能超过{0}位! javax.faces.validator.LengthValidator.MINIMUM={1}: 校验失败: 字符串长度必须大于{0}位! javax.faces.validator.DoubleRangeValidator.NOT_IN_RANGE={2}: 校验失败:您输入的值必须 处于{0}和{1}之间。 javax.faces.validator.LongRangeValidator.NOT_IN_RANGE={2}: 校验失败:您输入的值必须处于 {0}和{1}之间。
同样需要使用native2ascii.exe工具来处理上面资源文件,处理后新生成的文件名为“crazyit Messages_ zh_CN.properties”。
提供上面资源文件之后,还需要在faces-config.xml文件中指定用该文件覆盖默认的错误消息,这个步骤与覆盖类型转换后的错误消息完全相同。因此,我们在faces-config.xml文件中增加如下配置片段即可。
程序清单:codes\03\3.5\CustomMsg\WEB-INF\faces-config.xml
<application> <!-- 配置自定义错误消息资源 --> <message-bundle>crazyitMessages</message-bundle> <!-- 配置该应用所支持的语言、国家Locale --> <locale-config> <default-locale>en_US</default-locale> <supported-locale>zh_CN</supported-locale> </locale-config> </application>
再次浏览上面的add.jsp页面,如果用户输入的值无法通过输入校验,将看到如图3.27所示的错误提示。
图3.27 覆盖默认的错误提示消息
除此之外,JSF也允许通过UI组件的validatorMessage属性来临时指定输入校验失败的错误提示。validatorMessage属性支持使用表达式语言,因此它既可以直接指定固定的字符串,也可以通过表达式语言来指定国际化消息。
提示
其实validatorMessage属性与converterMessage属性的作用大致相似,区别只是validator Message属性指定输入校验失败后的错误提示;而converterMessage属性指定类型转换失败后的错误提示。
例如,我们将上面的add.jsp页面代码改为如下形式。
程序清单:codes\03\3.5\AssignMsg\add.jsp
<h1>添加备案</h1> <h:form prependId="false"> 网站域名:<h:inputText value="#{registBean.name}" id="name" validatorMessage="域名长度必须在5~25之间"> <f:validateLength minimum="5" maximum="25"/> </h:inputText> <h:message for="name" style="color:red"/><br/> 运行时间(年):<h:inputText value="#{registBean.duration}" id="duration" validatorMessage="运行时间必须在0~30年之间"> <f:validateLongRange minimum="0" maximum="30"/> </h:inputText> <h:message for="duration" style="color:red"/><br/> 投入费用(千元/年):<h:inputText value="#{registBean.cost}" id="cost" validatorMessage="每年投入费用必须在0~20千元之间"> <f:validateDoubleRange minimum="0" maximum="20"/> </h:inputText> <h:message for="cost" style="color:red"/><br/> <h:commandButton value="添加备案" action="#{registBean.add}"/> </h:form>
上面页面代码中粗体字代码为每个输入组件临时指定了输入校验失败后的错误提示,如果用户输入的值没有通过输入校验,将看到如图3.28所示的页面。
图3.28 通过validatorMessage指定输入校验的错误提示消息
3.5.4 必填校验器
可能有读者感到奇怪,JSF怎么没有提供“必填”校验器呢?很多时候,我们经常要求用户必须为某个组件输入值,由此可见“必填”校验器非常重要。
实际上,JSF内置提供了“必填”校验器,只是它不是以单独的标签形式存在,而是以属性的形式存在——几乎所有输入组件都可指定如下两个属性:
required:该属性用于指定该组件的值是否必填。如果指定该属性为true,就相当于使用了必填校验。
requiredMessage:该属性用于指定违反“必填”校验规则系统输出的错误提示。如果没有指定该属性,应用默认输出消息资源文件中key为javax.faces.component.UIInput.REQUIRED的国际化消息。
提示
读者可以把requiredMessage属性的作用等同于validatorMessage属性的作用,区别只是前者为“必填”校验器临时指定错误提示消息;而后者为其他校验器临时指定错误提示信息。