有时会有一种工具,它可以真正知足开拓职员和架构师的需求。开拓职员在第一次下载这种工具当天就可以在自己的运用程序中开始利用这种工具。理论上来说,这种工具在开拓职员花费大量韶光来节制其用法之前就可以从中获益。架构师也很喜好这种工具,由于它可以将开拓职员导向更高理论层次的实现。Hibernate Annotations 的 Validator 组件便是一种这样的工具。
开始之前须要理解的内容
在阅读本文之前,该当对 Java 平台版本 5(尤其是注释)、JSP 2.0(由于本文中创建了一些标签文件,并在 TLD 中定义了一些函数,它们都是 JSP 2.0 的新特性)和 Hibernate 及 Spring 框架有一个基本的理解。请把稳纵然不该用 Hibernate 来实现持久性,也可以在自己的运用程序中利用 Hibernate Validator。
Java SE 5 为 Java 措辞供应了很多须要的增强功能,不过其他增强功能可能都不如 注释 这样潜力巨大。利用 注释,我们就终于具有了一个标准、一级的元数据框架为 Java 类利用。Hibernate 用户手工编写 .hbm.xml 文件已经很多年了(或者利用 XDoclet 来自动实现这个任务)。如果手工创建了 XML 文件,那就必须对每个所须要的持久属性都更新这两个文件(类定义和 XML 映射文档)。利用 HibernateDoclet 可以简化这个过程(请参看清单 1 给出的例子),但是这须要我们确认自己的 HibernateDoclet 版本支持要利用的 Hibernate 的版本。doclet 信息在运行时也是不可用的,由于它被编写到了 Javadoc 风格的注释中了。Hibernate Annotations,如图 2 所示,通过供应一个标准、简明的映射类的方法和所添加的运行时可用性来对这些办法进行改进。
清单 1. 利用 HibernateDoclet 的 Hibernate 映射代码
/ @hibernate.property column=\"大众NAME\"大众 length=\"大众60\"大众 not-null=\公众true\"大众 /public String getName() { return this.name;} / @hibernate.many-to-one column=\公众AGENT_ID\公众 not-null=\"大众true\"大众 cascade=\"大众none\公众 outer-join=\"大众false\公众 lazy=\公众true\"大众 /public Agent getAgent() { return agent;}/ @hibernate.set lazy=\"大众true\"大众 inverse=\"大众true\"大众 cascade=\"大众all\公众 table=\"大众DEPARTMENT\"大众 @hibernate.collection-one-to-many class=\"大众com.triview.model.Department\公众 @hibernate.collection-key column=\公众DEPARTMENT_ID\"大众 not-null=\"大众true\"大众 /public List<Department> getDepartment() { return department;}
清单 2. 利用 Hibernate Annotations 的 Hibernate 映射代码
@NotNull@Column(name = \公众name\公众)@Length(min = 1, max = NAME_LENGTH) // NAME_LENGTH is a constant declared elsewherepublic String getName() { return name;} @NotNull@ManyToOne(cascade = {CascadeType.MERGE }, fetch = FetchType.LAZY)@JoinColumn(name = \"大众agent_id\"大众)public Agent getAgent() { return agent;} @OneToMany(mappedBy = \"大众customer\"大众, fetch = FetchType.LAZY)public List<Department> getDepartment() { return department;}
如果利用 HibernateDoclet,那么直到天生 XML 文件或运行时才能捕获缺点。利用 注释,在编译时就可以检测出很多缺点;或者如果在编辑时利用了很好的 IDE,那么在编辑时就可以检测出部分缺点。在从头创建运用程序时,可以利用 hbm2ddl 工具为自己的数据库从 hbm.xml 文件中天生 DDL。一些主要的信息 —— 比如name 属性的最大长度必须是 60 个字符,或者 DDL 该当添加非空约束 —— 都被从 HibernateDoclet 项添加到 DDL 中。当利用注释时,我们可以以类似的办法自动天生 DDL。
只管这两种代码映射办法都可以利用,不过注释的上风更为明显。利用注释,可以用一些常量来指定长度或其他值。编译循环的速率更快,并且不须要天生 XML 文件。个中最大的上风是可以访问一些有用信息,例如运行时的非空注释或长度。除了清单 2 给出的注释之外,还可以指定一些验证的约束。所包含的部分约束如下:
@Max(value = 100)@Min(value = 0)@Past@Future@Email在适当条件下,这些注释会引起由 DDL 天生检讨约束。(显然,@Future 并不是一个适当的条件。)还可以根据须要创建定制约束注释。
验证和运用程序层
编写验证代码是一个烦人且耗时的过程。常日,很多开拓职员都会放弃在特定的层进行有效性验证,从而可以节省一些韶光;但是所节省的韶光是否能够填补在这个地方因忽略部分功能所引起的毛病却非常值得磋商。如果在所有运用程序层中创建并掩护验证所须要的韶光可以极大地减少,那么辩论的焦点就会转向是否要在多个层次中进行有效性验证。假设有一个运用程序,它让用户利用一个用户名、密码和信用卡号来创建一个帐号。在这个运用程序中所希望进行验证的组件如下:
视图: 通过 JavaScript 进行验证可以避免与做事器反复进行交互,这样可以供应更好的用户体验。用户可以禁用 JavaScript,因此这个层次的验证最好要有,但是却并不可靠。对所须要的域进行大略的验证是必须的。掌握器: 验证必须在做事器真个逻辑中进行处理。这个层次中的代码可以以适宜某个特定用场的办法处理验证。例如,在添加新用户时,掌握器可以在进行处理之前检讨指定的用户名是否已经存在。做事: 相对繁芜的业务逻辑验证常日都最适宜放到做事层中。例如,一旦有一个信用卡工具看起来有效,就该当利用信用卡处理做事对这个信用卡的信息进行确认。DAO: 在数据到达这个层次时,该当已经是有效的了。只管如此,实行一次快速检讨从而确保所须要的域都非空并且值也都在特定的范围或遵照特定的格式(例如 e-mail 地址域就该当包含一个有效的 e-mail 地址)也是非常有益的。在此处捕获缺点总比产生可以避免的 SQLException 缺点要好。DBMS: 这是常日可以忽略验证的地方。纵然当前正在构建的运用程序是数据库的惟一客户机,将来还可能会添加其他客户机。如果运用程序有一些 bug(大部分运用程序都可能会有 bug),那么无效的数据也可能会被发送给数据库。在这种情形中,如果交运,就可以找到无效的数据,并且须要剖析这些数据是否可以打消,以及如何打消。模型: 这是进行验证的一个空想地方,它不须要访问外部做事,也不须要理解持久性数据。例如,某业务逻辑可能会哀求用户至少供应一个联系信息,这可以是一个电话号码也可以是一个 e-mail 地址;可以利用模型层的验证来确保用户的确供应了这种信息。进行验证的一种范例方法是对大略的验证利用 Commons Validator,并在掌握器中编写其他一些验证逻辑。Commons Validator 可以天生 JavaScript 来对视图中的验证进行处理。但是 Commons Validator 也有自己的毛病:它只能处理大略的验证问题,并且将验证的信息都保存到了 XML 文件中。Commons Validator 被设计用来与 Struts 一起利用,而且没有供应一种大略的方法在运用程序层间重用验证的声明。
在方案有效性验证策略时,选择在缺点发生时大略地处理这些缺点是远远不足的。一种良好的设计同时还要通过天生一个友好的用户界面来防止涌现缺点。采取预前辈行的方法进行验证可以极大地增强用户对付运用程序的理解。不幸的是,Commons Validator 并没有对此供应支持。假设希望 HTML 文件设置文本域的 maxlength 属性来与验证匹配,或者在文本域之后放上一个百分号(%)来表示要输入百分比的值。常日,这些信息都被硬编写到 HTML 文档中了。如果决定修正 name 属性来支持 75 个字符,而不是 60 个字符,那么须要改动多少地方呢?在很多运用程序中,常日都须要:
更新 DDL 来增大数据库列的长度(通过 HibernateDoclet、 hbm.xml 或 Hibernate Annotations)。更新 Commons Validator XML 文件将最大值增加到 75。更新所有与这个域有关的 HTML 表单,以修正 maxlength 属性。更好的方法是利用 Hibernate Validator。验证的定义都被通过注释 添加到了模型层中,同时还有对所包含的验证处理的支持。如果选择充分利用所有的 Hibernate,这个 Validator 就可以在 DAO 和 DBMS 层也供应验证。不才面给出的样例代码中,将利用 reflection 和 JSP 2.0 标签文件多实行一个步骤,从而充分利用注释 为视图层动态天生代码。这可以打消在视图中利用的硬编写的业务逻辑。
在清单 3 中,dateOfBirth 被注释为 NotNull 和过去的日期。 Hibernate 的 DDL 天生代码对这个列添加了一个非空约束,以及一个哀求日期必须是之前日期的检讨约束。e-mail 地址也是非空的,必须匹配 e-mail 地址的格式。这会天生一个非空约束,但是不会天生匹配这种格式的检讨约束。
清单 3. 通过 Hibernate Annotations 进行映射的大略联系办法
/ A Simplified object that stores contact information. @author Ted Bergeron @version $Id: Contact.java,v 1.1 2006/04/24 03:39:34 ted Exp $ /@MappedSuperclass@Embeddablepublic class Contact implements Serializable { public static final int MAX_FIRST_NAME = 30; public static final int MAX_MIDDLE_NAME = 1; public static final int MAX_LAST_NAME = 30; private String fname; private String mi; private String lname; private Date dateOfBirth; private String emailAddress; private Address address; public Contact() { this.address = new Address(); } @Valid @Embedded public Address getAddress() { return address; } public void setAddress(Address a) { if (a == null) { address = new Address(); } else { address = a; } } @NotNull @Length(min = 1, max = MAX_FIRST_NAME) @Column(name = \"大众fname\"大众) public String getFirstname() { return fname; } public void setFirstname(String fname) { this.fname = fname; } @Length(min = 1, max = MAX_MIDDLE_NAME) @Column(name = \"大众mi\公众) public String getMi() { return mi; } public void setMi(String mi) { this.mi = mi; } @NotNull @Length(min = 1, max = MAX_LAST_NAME) @Column(name = \"大众lname\公众) public String getLastname() { return lname; } public void setLastname(String lname) { this.lname = lname; } @NotNull @Past @Column(name = \公众dob\"大众) public Date getDateOfBirth() { return dateOfBirth; } public void setDateOfBirth(Date dateOfBirth) { this.dateOfBirth = dateOfBirth; } @NotNull @Email @Column(name = \"大众email\公众) public String getEmailAddress() { return emailAddress; } public void setEmailAddress(String emailAddress) { this.emailAddress = emailAddress; }
样例运用程序
在 下载 一节,您可以下载一个样例运用程序,它展示了本文中采取的设计思想和代码。由于这是一个可以事情的运用程序,因此代码比本文中谈论的的内容更为繁芜。例如,清单 9 就节选于标签文件 text.tag;这个样例运用程序具有标签文件利用的所有代码,以及其他三个类似的标签文件利用的代码(用于选择、隐蔽和检讨框的 HTML 元素)。由于这是一个可以事情的运用程序,它包含了一个在这种类型的运用程序中都可以找到的架构。还有一个 Ant 构建文件、Spring 和 Hibernate XML 封装代码,以及 log4j 配置。虽然这些都不是本文先容的重点,但是您会创造仔细研究一下这个样例运用程序的源代码是非常有用的。
如果须要,Hibernate DAO 实现也可以利用 Validation Annotations。所需做的是在 hibernate.cfg.xml 文件中指定基于 Hibernate 事宜的验证规则。(更多信息请参考 Hibernate Validator 的文档;可以在 参考资料一节中找到干系的链接)。如果真地希望抄近路,您可以只捕获做事或掌握器中的 InvalidStateException 非常,并循环遍历 InvalidValue数组。
对掌握器添加验证
要实行验证,须要创建一个 Hibernate 的 ClassValidator 实例。这个类进行实例化的代价可能会很高,因此最好只对希望进行验证的每个类来进行实例化。一种方法是创建一个实用工具类,对每个模型工具存储一个 ClassValidator 实例,如清单 4 所示:
清单 4. 处理验证的实用工具类
/ Handles validations based on the Hibernate Annotations Validator framework. @author Ted Bergeron @version $Id: AnnotationValidator.java,v 1.5 2006/01/20 17:34:09 ted Exp $ /public class AnnotationValidator { private static Log log = LogFactory.getLog(AnnotationValidator.class); // It is considered a good practice to execute these lines once and // cache the validator instances. public static final ClassValidator<Customer> CUSTOMER_VALIDATOR = new ClassValidator<Customer>(Customer.class); public static final ClassValidator<CreditCard> CREDIT_CARD_VALIDATOR = new ClassValidator<CreditCard>(CreditCard.class); private static ClassValidator<? extends BaseObject> getValidator(Class<? extends BaseObject> clazz) { if (Customer.class.equals(clazz)) { return CUSTOMER_VALIDATOR; } else if (CreditCard.class.equals(clazz)) { return CREDIT_CARD_VALIDATOR; } else { throw new IllegalArgumentException(\"大众Unsupported class was passed.\"大众); } } public static InvalidValue[] getInvalidValues(BaseObject modelObject) { String nullProperty = null; return getInvalidValues(modelObject, nullProperty); } public static InvalidValue[] getInvalidValues(BaseObject modelObject, String property) { Class<? extends BaseObject>clazz = modelObject.getClass(); ClassValidator validator = getValidator(clazz); InvalidValue[] validationMessages; if (property == null) { validationMessages = validator.getInvalidValues(modelObject); } else { // only get invalid values for specified property. // For example, \公众city\"大众 applies to getCity() method. validationMessages = validator.getInvalidValues(modelObject, property); } return validationMessages; }}
在清单 4 中,创建了两个 ClassValidator,一个用于 Customer,其余一个用于 CreditCard。这两个希望进行验证的类可以调用 getInvalidValues(BaseObject modelObject),会返回 InvalidValue[]。这则会返回一个包含模型工具实例缺点的数组。其余,这个方法也可以通过供应一个特定的属性名来调用,这样做会只返回与该域有关的缺点。
在利用 Spring MVC 和 Hibernate Validator 时,为信用卡创建一个验证过程变得非常大略,如清单 5 所示:
清单 5. Spring MVC 掌握器利用的 CreditCardValidator
/ Performs validation of a CreditCard in Spring MVC. @author Ted Bergeron @version $Id: CreditCardValidator.java,v 1.2 2006/02/10 21:53:50 ted Exp $ /public class CreditCardValidator implements Validator { private CreditCardService creditCardService; public void setCreditCardService(CreditCardService service) { this.creditCardService = service; } public boolean supports(Class clazz) { return CreditCard.class.isAssignableFrom(clazz); } public void validate(Object object, Errors errors) { CreditCard creditCard = (CreditCard) object; InvalidValue[] invalids = AnnotationValidator.getInvalidValues(creditCard); // Perform \"大众expensive\"大众 validation only if no simple errors found above. if (invalids == null || invalids.length == 0) { boolean validCard = creditCardService.validateCreditCard(creditCard); if (!validCard) { errors.reject(\"大众error.creditcard.invalid\"大众); } } else { for (InvalidValue invalidValue : invalids) { errors.rejectValue(invalidValue.getPropertyPath(), null, invalidValue.getMessage()); } } }}
validate() 方法只须要将 creditCard 实例通报给这个验证过程,从而返回 InvalidValue 数组。如果创造了一个或多个这种大略缺点,那么就可以将 Hibernate 的 InvalidValue 数组转换成 Spring 的 Errors 工具。如果用户已经创建了这个信用卡并且没有涌现任何大略缺点,就可以将更加彻底的验证委托给做事层进行。这一层可以与商业做事供应者一起对信用卡进行验证。
现在我们已经看到这个大略的模型层注释是如何平衡到掌握器、DAO 和 DBMS 层的验证的。在 HibernateDoclet 和 Commons Validator 中创造的验证逻辑的重合现在都已经统一到模型中了。只管这是一个非常受欢迎的改进,但是视图层传统上来说一贯是最须要进行详细验证的地方。
为视图添加验证
不才面的例子中,利用了 Spring MVC 和 JSP 2.0 标签文件。JSP 2.0 许可在 TLD 文件中对定制函数进行注册,并在一个标签文件中进行调用。标签文件类似于 taglibs,但是它们是利用 JSP 代码编写的,而不是利用 Java 代码编写的。采取这种方法,利用 Java 措辞写好的代码就可以封装成函数,而利用 JSP 写好的代码则可以放入标签文件中。在这种情形中,对注释的处理须要利用映像,这会由几个函数来实行。绑定 Spring 或呈现 XHTML 的代码也是标签文件的一部分。
清单 6 中节选的 TLD 代码定义 text.tag 文件可以利用,并定义了一个名为 required 的函数。
清单 6. 创建表单 TLD
<?xml version=\公众1.0\公众 encoding=\"大众ISO-8859-1\"大众 ?><taglib xmlns=\公众http://java.sun.com/xml/ns/j2ee\"大众 xmlns:xsi=\"大众http://www.w3.org/2001/XMLSchema-instance\"大众 xsi:schemaLocation=\公众http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd\"大众 version=\"大众2.0\公众> <tlib-version>1.0</tlib-version> <short-name>form</short-name> <uri>formtags</uri> <tag-file> <name>text</name> <path>/WEB-INF/tags/form/text.tag</path> </tag-file> <function> <description>determine if field is required from Annotations</description> <name>required</name> <function-class>com.triview.web.Utilities</function-class> <function-signature>Boolean required(java.lang.Object,java.lang.String) </function-signature></function> </taglib>
清单 7 节选自 Utilities 类,个中包含了标签文件利用的所有函数。在前文中我们曾经说过,最适宜利用 Java 代码编写的代码都被放到了几个 TLD 可以映射的函数中,这样标签文件就可以利用它们了;这些函数都是在 Utilities 类中进行编码的。因此,我们须要三样东西:定义这些类的 TLD 文件、Utilities 中的函数,以及标签文件本身,后者要利用这些函数。(第四样该当是利用这个标签文件的 JSP 页面。)
在清单 7 中,给出了在 TLD 中引用的函数和其余一个表示给定属性是否是 Date 的方法。在这个类中要涉及到比较多的代码,但是本文限于篇幅,不会给出所有的代码;不过须要把稳 findGetterMethod() 除了将表达式措辞(Expression Language,EL)方法表示(customer.contact)转换成 Java 表示(customer.getContact())之外,还实行了基本的映像操作。
清单 7. Utilities 节选
public static Boolean required(Object object, String propertyPath) { Method getMethod = findGetterMethod(object, propertyPath); if (getMethod == null) { return null; } else { return getMethod.isAnnotationPresent(NotNull.class); }} public static Boolean isDate(Object object, String propertyPath) { return java.util.Date.class.equals(getReturnType(object, propertyPath));} public static Class getReturnType(Object object, String propertyPath) { Method getMethod = findGetterMethod(object, propertyPath); if (getMethod == null) { return null; } else { return getMethod.getReturnType(); }}
此处可以清楚地看到在运行时利用 Validation annotations 是多么随意马虎。可以大略地引用工具的 getter 方法,并检讨这个方法是否有干系的给定的注释 。
清单 8 中给出的 JSP 例子进行了简化,这样就可以着重查看干系的部分了。此处,这里有一个表单,它有一个选择框和两个输入域。所有这些域都是通过在 form.tld 文件中声明的标签文件进行呈现的。标签文件被设计成利用智能缺省值,这样就可以根据须要许可大略编码的 JSP 可以有定义更多信息的选项。关键的属性是 propertyPath,它利用 EL 符号将这个域映射为模型层属性,就像是利用 Spring MVC 的 bind 标签一样。
清单 8. 一个包含表单的大略 JSP 页面
<%@ taglib tagdir=\"大众/WEB-INF/tags/form\"大众 prefix=\公众form\"大众 %> <form method=\"大众post\"大众 action=\"大众<c:url value=\"大众/signup/customer.edit\"大众/>\"大众> <form:select propertyPath=\公众creditCard.type\"大众 collection=\"大众${creditCardTypeCollection}\"大众 required=\"大众true\公众 labelKey=\"大众prompt.creditcard.type\公众/> <form:text propertyPath=\公众creditCard.number\"大众 labelKey=\"大众prompt.creditcard.number\"大众> <img src=\"大众<c:url value=\"大众/images/icons/help.png\公众/>\"大众 alt=\"大众Help\"大众 onclick=\公众new Effect.SlideDown('creditCardHelp')\"大众/></form:text> <form:text propertyPath=\"大众creditCard.expirationDate\公众/></form>
text.tag 文件的完全源代码太大了,不好放在这儿,因此清单 9 给出了个中关键的部分:
清单 9. 标签文件 text.tag 节选
<%@ attribute name=\"大众propertyPath\"大众 required=\"大众true\"大众 %> <%@ attribute name=\公众size\"大众 required=\"大众false\"大众 type=\公众java.lang.Integer\"大众 %> <%@ attribute name=\公众maxlength\公众 required=\公众false\"大众 type=\"大众java.lang.Integer\公众 %> <%@ attribute name=\"大众required\"大众 required=\公众false\"大众 type=\公众java.lang.Boolean\"大众 %> <%@ taglib uri=\公众http://www.springframework.org/tags\"大众 prefix=\"大众spring\"大众 %> <%@ taglib uri=\"大众formtags\"大众 prefix=\"大众form\"大众 %> <c:set var=\公众objectPath\"大众 value=\"大众${form:getObjectPath(propertyPath)}\公众/> <spring:bind path=\"大众${objectPath}\"大众> <c:set var=\"大众object\公众 value=\"大众${status.value}\"大众/> <c:if test=\"大众${object == null}\"大众> <%-- Bind ignores the command object prefix, so simple properties of the command object return null above. --%> <c:set var=\公众object\"大众 value=\"大众${commandObject}\"大众/> <%-- We depend on the controller adding this to request. --%> </c:if> </spring:bind> <%-- If user did not specify whether this field is required, query the object for this info. --%> <c:if test=\"大众${required == null}\"大众> <c:set var=\"大众required\"大众 value=\"大众${form:required(object,propertyPath)}\"大众/> </c:if> <c:choose> <c:when test=\"大众${required == null || required == false}\公众> <c:set var=\"大众labelClass\"大众 value=\"大众optional\"大众/> </c:when> <c:otherwise> <c:set var=\公众labelClass\"大众 value=\公众required\"大众/> </c:otherwise> </c:choose> <c:if test=\"大众${maxlength == null}\公众> <c:set var=\公众maxlength\"大众 value=\"大众${form:maxLength(object,propertyPath)}\公众/> </c:if> <c:set var=\公众isDate\"大众 value=\"大众${form:isDate(object,propertyPath)}\"大众/> <c:set var=\公众cssClass\"大众 value=\公众input_text\公众/> <c:if test=\公众${isDate}\"大众> <c:set var=\公众cssClass\"大众 value=\公众input_date\"大众/> </c:if> <div class=\"大众field\"大众> <spring:bind path=\公众${propertyPath}\"大众> <label for=\"大众${status.expression}\公众 class=\"大众${labelClass}\"大众><fmt:message key=\"大众prompt.${propertyPath}\"大众/></label> <input type=\公众text\"大众 name=\"大众${status.expression}\"大众 value=\"大众${status.value}\"大众 id=\"大众${status.expression}\"大众<c:if test=\公众${size != null}\公众> size=\"大众${size}\"大众</c:if> <c:if test=\"大众${maxlength != null}\公众> maxlength=\"大众${maxlength}\公众</c:if> class=\"大众${cssClass}\公众/> <c:if test=\公众${isDate}\公众> <img id=\公众${status.expression}_button\"大众 src=\公众<c:url value=\公众/images/icons/calendar.png\"大众/>\"大众 alt=\"大众calendar\"大众 style=\"大众cursor: pointer;\公众/> <script type=\公众text/javascript\公众> Calendar.setup( { inputField : \"大众${status.expression}\"大众, // ID of the input field ifFormat : \公众%m/%d/%Y\"大众, // the date format button : \公众${status.expression}_button\"大众 // ID of the button } ); </script> </c:if> <span class=\"大众icons\"大众><jsp:doBody/></span> <c:if test=\公众${status.errorMessage != null && status.errorMessage != ''}\公众> <p class=\"大众fieldError\"大众><img id=\公众${status.expression}_error\"大众 src=\"大众<c:url value=\公众/images/icons/error.png\"大众/>\"大众 alt=\"大众error\公众/>${status.errorMessage}</p> </c:if> </spring:bind> </div>
我们立时就可以看出 propertyPath 是惟一须要的属性。size、 maxlength 和 required 都可以忽略。objectPath var 被设置为在 propertyPath 中引用的属性的父工具。因此,如果 propertyPath 是 customer.contact.fax.number, 那么 objectPath 就该当被设置为 customer.contact.fax。我们现在就利用 Spring 的 bind 标签绑定到了包含属性的工具上。这会将工具变量设置成对包含属性的实例的引用。接下来,检讨这个标签的用户是否已经指定他/她们是否希望属性是必须的。许可表单开拓职员覆盖从注释中返回的值是非常主要的,由于有时他/她们希望让掌握器为所须要的域设置缺省值,而用户可能并不肯望为这个域供应值。如果表单开拓职员没有为 required 指定值,那么就可以调用这个表单 TLD 的 required 函数。这个函数调用了在 TLD 文件中映射的方法。这个方法大略地检讨 @NotNull 注释;如果它创造某个属性具有这个注释,就将 labelClass 变量设置为必须的。可以类似地确定精确的 maxlength 以及这个域是否是一个 Date。
接下来利用 Spring 来绑定到 propertyPath 上,而不是像前面一样只绑定到包含这个属性的工具上。这许可在天生 label 和 input HTML 标签时利用 status.expression 和 status.value。 input 标签也可以利用一个大小 maxlength 以及适当的类来天生。如果前面已经确定属性是一个 Date,现在就可以添加 JavaScript 日历了。(可以在 参考资料 一节找到一个很好的日历组件的链接)。把稳根据须要链接属性、输入 ID 和图像 ID 的标签是多么大略。)这个 JavaScript 日历须要一个图像 ID 来匹配输入域,其后缀是 _button。
末了,可以将 <jsp:doBody/> 封装到一个 span 标签中,这样许可表单开拓职员在页面中添加其他图标,例如用来寻求帮助的图标。(清单 8 给出了一个为信用卡号域添加的帮助图标。)末了的部分是检讨 Spring 是否为这个属性报告和显示了一个缺点,并和一个缺点图标一起显示。
利用 CSS,就可以对必须的域进行一下装饰 —— 例如,让它们以赤色显示、在文本边上显示一个星号,或者利用一个背景图像来装饰它。在清单 10 中,将必须的域的标签设置成玄色,而且后面显示一个赤色的星号(在 Firefox 以及其他标准兼容的浏览器中),如果是在 IE 中则还会在左边加上一个小旌旗的背景图像:
清单 10. 对必须域进行装饰的 CSS 代码
label.required { color: black; background-image: url( /images/icons/flag_red.png ); background-position: left; background-repeat: no-repeat;}label.required:after { content: '';}label.optional { color: black;}
日期输入域自动会在右边放上一个 JavaScript 日历图标。对所有的文本域设置精确的 maxlength 属性可以防止用户输入太多文本所引起的缺点。可以扩展 text 标签来为输入域类设置其他的数据类型。可以修正 text 标签利用 HTML,而不是 XHTML(如果希望这样)。可以不太费力地得到具有精确语义的 HTML 表单,而且不需学习基于组件的框架知识,就可以利用基于组件的 Web 框架的优点。
只管标签文件天生的 HTML 文件可以帮助防止一些缺点的产生,但是在视图层并没有任何代码来真正进行缺点检讨。由于可以利用类属性,现在就可以添加一些大略的 JavaScript 来实现这种功能了,如清单 11 所示。这里的 JavaScript 也可以是通用的,在任一表单中都可以重用。
清单 11. 大略的 JavaScript 验证程序
<script type=\"大众text/javascript\"大众> function checkRequired(form) { var requiredLabels = document.getElementsByClassName(\公众required\"大众, form); for (i = 0; i < requiredLabels.length; i++) { var labelText = requiredLabels[i].firstChild.nodeValue; // Get the label's textvar labelFor = requiredLabels[i].getAttribute(\"大众for\公众); // Grab the for attributevar inputTag = document.getElementById(labelFor); // Get the input tag if (inputTag.value == null || inputTag.value == \"大众\"大众) { alert(\"大众Please make sure all required fields have been entered.\"大众); return false; // Abort Submit } } return true; }</script>
这个 JavaScript 是通过为表单声明添加 onsubmit=\公众return checkRequired(this);\"大众 被调用的。这个脚本大略地获取具有所须要的类的表单中的所有元素。由于我们的习气是在标签标记中利用这个类,因此代码会通过 for 属性来查找与这个标签连接在一起的输入域。如果任何输入域为空,就会天生一条大略的警告,表单提交就会取消。可以大略地对这个脚本进行扩充,使其扫描多个类,并相应地进行验证。
对付基于 JavaScript 的综合的验证凑集来说,最好是利用开源实现,而不是自行开拓。在清单 8 中您可能已经把稳到下面的代码:
onclick=\"大众new Effect.SlideDown('creditCardHelp')\"大众
这个函数是 Script.aculo.us 库的一部分,这个库供应了很多高等的效果。如果正在利用 Script.aculo.us,就须要对所构建的内容利用 Prototype 库。 JavaScript 验证库的一个例子是由 Andrew Tetlaw 在 Prototype 根本上构建的。(请参看 参考资料 一节中的链接。)他的框架依赖于被添加到输入域的类:
<input class=\公众required validate-number\"大众 id=\"大众field1\"大众 name=\"大众field1\"大众 />
可以大略地修正 text.tag 的逻辑在 input 标签中插入几个类。将 class=\公众required\"大众 添加到输入标签和 label 标签中不会影响 CSS 规则,但会毁坏清单 10 中给出的大略 JavaScript 验证程序。如果要稠浊利用框架中的代码和大略的 JavaScript 代码,最好利用不同的类名,或在利用类名搜索元素时确保类名有效并检讨标签类型。
末了的考虑
本文已经先容了模型层的注释如何充分用来在视图、掌握器、DAO 和 DBMS 层中创建验证。必须手工创建做事层的验证,例如信用卡验证。其他模型层的验证,例如逼迫属性 C 是必须的,而属性 A 和 B 都处于指定的状态,这也是一个手工任务。然而,利用 Hibernate Annotations 的 Validator 组件,就可以轻松地声明并运用大多数验证。
展望
不论是大略例子还是所引用框架的 JavaScript 验证都可以对大略的条件进行检讨,例如域必须要填写,或者客户机端代码中的数据类型必须要匹配预期的类型。须要用到做事器端逻辑的验证可以利用 Ajax 添加到 JavaScript 验证程序中。您可以利用一个用户注册界面来让用户可以选择用户名。文本标签可以进行增强来检讨 @Column(unique = true) 注释。在找到这个注释时,标签可以添加一个用来触发 Ajax 调用的类。
现在您不须要在运用程序层间掩护重复的验证逻辑了,这样就可以节省出大量的开拓韶光。想像一下您终极可以为运用程序所能添加的增强功能!