dedecms织梦内容管理系统    
首页 | java | C/C++ | PHP | 操作系统 | ajax | 脚本编程 | 安全技术 | 本站下载页 | flex | CRM | 专题 | QQ群 | 测试中心 | 会员中心 | 积分规则
  当前位置:主页>java>开源框架>文章内容
JSF 检验器
来源: 作者:

转换和检验的主要用途是,在更新模型数据之前,确保值符合要求。这样,在调用应用程序方法来处理数据时,就可以对模型的状态做某些假设。通过使用转换和检验,可以集中精力考虑业务逻辑,而不必为输入数据的限制条件(比如空值检测、长度限制、范围边界等等)操心。

所以,应该在更新模型数据阶段中将组件数据绑定到托管 bean 模型之前执行转换和检验。正如在 “JSF 应用程序的生命周期” 一节中看到的,在处理检验阶段进行转换和检验 — 先转换,再检验。

在 JSF 中有四种检验形式:

  • 内置的检验组件
  • 应用程序级检验
  • 后端 bean 中的检验方法(内联)
  • 定制的检验组件(它们实现 Validator 接口)

本节解释这些检验形式并演示它们的使用方法。

标准检验

JSF 提供三个标准检验组件:

  • DoubleRangeValidator:组件的本地值必须是数字类型的;必须处于最小值、最大值或这两者指定的范围内。
  • LongRangeValidator:组件的本地值必须是数字类型的,并可以转换为 long;必须处于最小值、最大值或这两者指定的范围内。
  • LengthValidator:类型必须是 string;长度必须处于最小值、最大值或这两者指定的范围内。

在这个示例应用程序中,联系人的年龄可以是任何有效的整数。因为 -2 这样的年龄是没有意义的,所以需要给这个字段添加某些检验。清单 29 使用 <f:validateLongRange> 进行简单的检验,确保年龄字段中的数据是有意义的:


清单 29. 使用 <f:validateLongRange> 检验年龄的值是否合理

<%-- age --%>
<h:outputLabel value="Age" for="age" accesskey="age" />
<h:inputText id="age" size="3" value="#{contactController.contact.age}">
<f:validateLongRange minimum="0" maximum="150"/>
</h:inputText>
<h:message for="age" errorClass="errorClass" />

在检验年龄字段之后,可能希望为名字字段指定长度限制,见清单 30。


清单 30. 确保 firstName 不是太长也不是太短

<%-- First Name --%>
<h:outputLabel value="First Name" for="firstName" accesskey="f" />
<h:inputText id="firstName" label="First Name" required="true"
value="#{contactController.contact.firstName}" size="10" >
<f:validateLength minimum="2" maximum="25" />
</h:inputText>
<h:message for="firstName" errorClass="errorClass" />

尽管 JSF 内置的检验在许多场景中都是有效的,但是它们的功能有限。在处理电子邮件、电话号码、URL、日期等数据时,编写自己的检验器可能更好(本节后面会讨论定 制的检验器)。还可以使用 Tomahawk、Shale、JSF-Validations 和 Crank 提供的检验器(参见 参考资料)。

应用程序级检验

从 概念上说,应用程序级检验实际上是业务逻辑检验。JSF 将表单级或字段级检验与业务逻辑检验分隔开。应用程序级检验需要在使用模型的托管 bean 方法中添加代码,以确保绑定到模型的数据的质量。例如,对于购物车来说,可以使用表单级检验确保输入的数量是有效的,但是还需要通过业务逻辑检验检查用户 是否超过了他的信用限额。这是 JSF 中关注点隔离的另一个例子。

假设用户单击一个绑定到动作方法的按钮,这个动作方法在调用应用程序阶段被调用(细节参见前面的 图 5)。在对模型数据进行任何操作之前(通常在更新模型阶段更新模型数据),可以根据应用程序的业务逻辑检查输入的数据是否是有效的。

例如,在这个示例应用程序中,用户单击 Update/Add 按钮,这个按钮绑定到应用程序控制器的 persist() 方法。可以在 persist() 方法中添加检验代码,检查系统中是否已经存在当前的 firstName/lastName 组合。如果这个联系人已经存在,那么可以在 FacesContext 中添加一个消息,然后返回 null(如果在这个动作上应用了导航规则),从而让 JSF 留在当前视图上。

我们再看一下联系人应用程序,这一次在 persist() 动作方法中执行一些应用程序级逻辑,见清单 31 和清单 32。清单 31 给出控制器中的应用程序级检验逻辑:


清单 31. 控制器中的应用程序级检验逻辑

public class ContactController {
public String persist() {

/* Perform the application level validation. */
try {
contact.validate();
} catch (ContactValidationException contactValidationException) {
addErrorMessage(contactValidationException.getLocalizedMessage());
return null;
}


/* Turn form off, turn link on. */
form.setRendered(false);
addNewCommand.setRendered(true);


/* Add a status message. */
if (contactRepository.persist(contact) == null) {
addStatusMessage("Added " + contact);
} else {
addStatusMessage("Updated " + contact);
}
return "contactPersisted";
}
private void addErrorMessage(String message) {
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(
FacesMessage.SEVERITY_ERROR, message, null));
}

在清单 31 中,persist() 方法调用 contact 对象上的 validate() 方法。它捕获任何异常并把异常错误消息转换为 FacesMessage。如果发生异常,它会返回 null,其含义为:留在当前视图上,不要导航到下一个视图

实际的检验代码包含在模型中 — 即,Contact 类的 validate() 方法,见清单 32。这一点很重要:在为联系人添加更多的检验代码时,不需要修改控制器或视图层。


清单 32. 检验代码在模型中,而不在控制器中

...
public class Contact implements Serializable {
...
public void validate() throws ContactValidationException {
if (
(homePhoneNumber == null || "".equals(homePhoneNumber)) &&
(workPhoneNumber == null || "".equals(workPhoneNumber)) &&
(mobilePhoneNumber == null || "".equals(mobilePhoneNumber))
) {
throw new ContactValidationException("At least one phone number" +
"must be set");

}
}

应用程序级检验很简单,也很容易使用。它的优点是:

  • 容易实现
  • 不需要单独的类(定制检验器)
  • 页面作者不需要指定检验器

应用程序级检验的缺点是,它在其他形式的检验(标准、定制和组件)之后执行,而且错误消息只在执行其他形式的检验之后显示。

最后,应用程序级检验应该只用于需要业务逻辑检验的场合。

后端 bean 中的定制检验器

对 于标准 JSF 检验器不支持的数据类型(包括电子邮件地址和 ZIP 编码),需要构建自己的检验组件。如果希望对显示给最终用户的检验消息进行显式地控制,也需要构建自己的检验器。通过使用 JSF,可以创建可插入的检验组件,可以在整个 Web 应用程序中重用这些组件。

如果不想创建单独的检验器类,也可以在后端 bean 方法中实现定制的检验。这种方式对于应用程序开发人员更合适。例如,可以在托管 bean 中编写一个方法来检验电话号码,见清单 33:


清单 33. 电话号码检验

public class ContactValidators {
private static Pattern phoneMask;

static {
String countryCode = "^[0-9]{1,2}";
String areaCode = "(|-|\\(){1,2}[0-9]{3}(|-|\\)){1,2}";
String prefix = "(|-)?[0-9]{3}";
String number = "(|-)[0-9]{4}___FCKpd___4quot;;
phoneMask = Pattern.compile(countryCode + areaCode + prefix + number);
}

public void validatePhone(FacesContext context, UIComponent component,
Object value) throws ValidatorException {

String sValue = (String)value;

Matcher matcher = phoneMask.matcher(sValue);

if (!matcher.matches()) {
FacesMessage message = new FacesMessage();
message.setDetail("Phone number not valid");
message.setSummary("Phone number not valid");
message.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(message);
}

}
... //ADD MORE VALIDATION METHODS FOR THE APP HERE!

}

ContactValidators 类有一个 validatePhone() 方法。validatePhone() 方法使用 Java regex API 确保输入的字符串是有效的电话号码。如果值与模式不匹配,那么 validatePhone() 方法会抛出一个 ValidatorException

要使用 ContactValidators 类,需要在 faces-config.xml 文件中注册它,见清单 34:


清单 34. 将 ContactValidators 注册为托管 bean

<managed-bean>
<managed-bean-name>contactValidators</managed-bean-name>
<managed-bean-class>com.arcmind.contact.validators.ContactValidators</managed-bean-class>
<managed-bean-scope>application</managed-bean-scope>
</managed-bean>

要使用检验器,需要对工作电话号码、家庭电话号码和移动电话号码使用 validator 属性,见清单 35:


清单 35. 通过 validator 属性在视图中使用检验器

<%-- Work --%>
<h:outputLabel value="Work" for="work" accesskey="w" />
<h:inputText id="work"
value="#{contactController.contact.workPhoneNumber}" size="11"
validator="#{contactValidators.validatePhone}" />
<h:message for="work" errorClass="errorClass" />
<%-- Home --%>
<h:outputLabel value="Home" for="home" accesskey="h" />
<h:inputText id="home"
value="#{contactController.contact.homePhoneNumber}" size="11"
validator="#{contactValidators.validatePhone}" />
<h:message for="home" errorClass="errorClass" />
<%-- Mobile --%>
<h:outputLabel value="Mobile" for="mobile" accesskey="m" />
<h:inputText id="mobile"
value="#{contactController.contact.mobilePhoneNumber}" size="11"
validator="#{contactValidators.validatePhone}" />
<h:message for="mobile" errorClass="errorClass" />

可以看到,这里把 validatePhone() 方法绑定到 <h:inputText> 组件:<h:inputText id="mobile" ... validator="#{contactValidators.validatePhone}"

对于应用程序开发人员来说,使用托管 bean 执行检验是不错的方法。但是,如果要开发可重用的框架或可重用的组件集,那么最好创建单独的定制检验器。


单独的定制检验器

可以使用 JSF 创建可插入的检验组件,这些组件可以在整个 Web 应用程序中重用。

要创建定制的检验器,需要执行以下步骤:

  1. 创建一个实现 Validator 接口(javax.faces.validator.Validator)的类。
  2. 实现 validate() 方法。
  3. 在 faces-config.xml 文件中注册定制的检验器。
  4. 在 JSP 中使用 <f:validator/> 标记。

我们来逐一介绍这些步骤,并提供创建定制检验器的示例代码。

步骤 1:实现 Validator 接口

第一步是实现 Validator 接口,见清单 36:


清单 36. 实现 Validator 接口

package com.arcmind.validators;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ZipCodeValidator implements Validator {

/** Accepts zip codes like 85710 */
private static final String ZIP_REGEX = "[0-9]{5}";

/** Optionally accepts a plus 4 */
private static final String PLUS4_OPTIONAL_REGEX = "([ |-]{1}[0-9]{4})?";

private static Pattern mask = null;

static {
mask = Pattern.compile(ZIP_REGEX + PLUS4_OPTIONAL_REGEX);
}

步骤 2:实现 validate() 方法

接下来,需要实现 validate() 方法,见清单 37:


清单 37. 实现 validate() 方法

public class ZipCodeValidator implements Validator {

...
public void validate(FacesContext context, UIComponent component,
Object value) throws ValidatorException {

/* Get the string value of the current field */
String zipField = (String) value;

/* Check to see if the value is a zip code */
Matcher matcher = mask.matcher(zipField);

if (!matcher.matches()) {

FacesMessage message = new FacesMessage();
message.setDetail("Zip code not valid");
message.setSummary("Zip code not valid");
message.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(message);
}

}
}

步骤 3:注册定制检验器

现在,您应该对向 FacesContext 注册定制检验器的代码很熟悉了(见清单 38):


清单 38. 在 faces-config.xml 中注册定制检验器

<validator>
<validator-id>arcmind.zipCode</validator-id>
<validator-class>com.arcmind.validators.ZipCodeValidator</validator-class>
</validator>

步骤 4:在 JSP 中使用 <f:validator> 标记

<f:validator/> 标记声明使用 zipCode 检验器,见清单 39:


清单 39. 在 JSP 中使用 <f:validator> 标记

<%-- zip --%>
<h:outputLabel value="Zip" for="zip" accesskey="zip" />
<h:inputText id="zip" size="5"
value="#{contactController.contact.zip}">
<f:validator validatorId="arcmind.zipCode"/>
</h:inputText>
<h:message for="zip" errorClass="errorClass" />

总之,创建定制检验器是非常容易 的,而且这些检验器可以跨许多应用程序重用。缺点是必须创建一个单独的类,并在 faces 上下文中管理检验器的注册。但是,可以进一步改进定制检验器的实现:创建一个使用这个检验器的定制标记,使它看起来像内置的检验。对于需要经常检验的数 据,比如电子邮件地址,这种方法可以简化代码,尽可能增加代码重用和提高应用程序行为的一致性。

再论检验和转换

在到达检验阶段之前,转换已经执行过了。例如,如果有一个 int 属性绑定到 inputText 字段,那么先对这个字段进行转换,然后再进行检验。

假设您有一个 PhoneNumber 值对象,并使用它(而不是使用 String)在 Contact 中存储电话号码。那么 清单 33 中电话号码的检验规则就没什么意义了。实际上,这个检验规则只证明 String 采用了电话号码的格式。这个逻辑实际上应该放在转换器中,见清单 40:


清单 40. 再论检验和转换:PhoneConverter

package com.arcmind.contact.converter;

import java.util.regex.Pattern;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;

import com.arcmind.contact.model.PhoneNumber;

/**
* @author Richard Hightower
*
*/
public class PhoneConverter implements Converter {
private static Pattern phoneMask;
static {
String countryCode = "^[0-9]{1,2}";
String areaCode = "( |-|\\(){1,2}[0-9]{3}( |-|\\)){1,2}";
String prefix = "( |-)?[0-9]{3}";
String number = "( |-)[0-9]{4}___FCKpd___11quot;;
phoneMask = Pattern.compile(countryCode + areaCode + prefix + number);
}

public Object getAsObject(FacesContext context, UIComponent component,
String value) {
System.out.println("PhoneConverter.getAsObject()");

if (value.isEmpty()) {
return null;
}
/* Before we parse, let's see if it really is a phone number. */
if (!phoneMask.matcher(value).matches()) {
FacesMessage message = new FacesMessage();
message.setDetail("Phone number not valid");
message.setSummary("Phone number not valid");
message.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ConverterException(message);
}

/* Now let's parse the string and populate a phone number object. */
PhoneNumber phone = new PhoneNumber();
phone.setOriginal(value);
String[] phoneComps = value.split("[ ,()-]");
String countryCode = phoneComps[0];
phone.setCountryCode(countryCode);

if ("1".equals(countryCode) && phoneComps.length == 4) {
phone.setAreaCode(phoneComps[1]);
phone.setPrefix(phoneComps[2]);
phone.setNumber(phoneComps[3]);
} else if ("1".equals(countryCode) && phoneComps.length != 4) {
throw new ConverterException(new FacesMessage(
"No Soup for you butter fingers!"));
} else if (phoneComps.length == 1 && value.length() > 10){
phone.setCountryCode(value.substring(0,1));
phone.setAreaCode(value.substring(1,4));
phone.setPrefix(value.substring(4,7));
phone.setNumber(value.substring(7));
} else {
phone.setNumber(value);
}
return phone;
}

public String getAsString(FacesContext context, UIComponent component,
Object value) {
System.out.println("PhoneConverter.getAsString()");
return value.toString();
}
}

与检验器不同,转换器的好处是可以在 faces-config.xml 中注册(见 清单 27),让转换器连接到某个类。每当这个类出现在表达式语言(EL)值绑定中时,会自动使用这个转换器;不需要在 JSP 中添加 <f:converter>。新的电话号码转换器会自动应用于 PhoneNumber,不需要在视图中指定转换器。

原来的电话号码检验成了电话号码转换的一部分,您可能想知道电话号码检验器现在是什么样子。可以通过编写一个检验器来回答这个问题,它证明电话号码属于亚利桑那州,见清单 41:


清单 41. 确保电话号码属于亚利桑那州

package com.arcmind.contact.validators;

import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.validator.ValidatorException;

import com.arcmind.contact.model.PhoneNumber;


public class ContactValidators {

public void validatePhone(FacesContext context, UIComponent component,
Object value) throws ValidatorException {

System.out.println("ContactValidators.validatePhone()");
PhoneNumber phoneNumber = (PhoneNumber)value;

if (!phoneNumber.getAreaCode().equals("520")
&& !phoneNumber.getAreaCode().equals("602")) {
FacesMessage message = new FacesMessage();
message.setDetail("Arizona residents only");
message.setSummary("Arizona residents only");
message.setSeverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(message);
}

}

}

注意,与前面的检验器不同,这个电话号码检验器并不处理 String。在调用它之前,已经调用了转换器。因此,值并不是 String,而是 PhoneNumber


上一篇:JSF 生命周期、转换、检验和阶段监听器   下一篇:JSF 应用程序的生命周期
[收藏] [推荐] [评论(0条)] [返回顶部] [打印本页] [关闭窗口]  
用户名: 新注册) 密码: 匿名评论
评论内容:(不能超过250字,需审核后才会公布,请自觉遵守互联网相关政策法规。
 §最新评论
  热点文章
·关于JSF和Struts的讨论
·Struts教程-Struts模块化编程教
·Struts入门经验
·用科学的思维方法指导软件的设计
·Hibernate配置文件中映射元素详
·Spring中事件处理的小技巧
·struts2.0pring2.0 hibernate3.2
·struts2.0 spring2.0 hibernate3
·浅谈hibernate lazy fetch
·Hibernate的Fetch
·优化hibernate性能的几点建议
·Hibernate中的取策略延迟加载
  相关文章
·JSF 生命周期、转换、检验和阶段
·JSF 应用程序的生命周期
·JSF与Struts的异同
·JavaServer Faces框架使用的设计
·JSF优势
·J2EE学习者值得研究的开源项目
·深入探讨 Spring 与 Struts 的集
·struts标签使用举例-logic
·系统构建高性能J2EE应用的五种核
·把WebLogic EJB程序迁移到JBoss
·JasperReport与hibernate结合使
·优化hibernate性能的几点建议
  相关信息
copy right @ 百家拳软件项目研究室 2007 辽ICP备07011763