注释化了数据验证
![]() |
|
U别: 中
Ted Bergeron
(ted@triview.com), 合作创始? Triview, Inc.
2006 q?10 ?10 ?/p>
管?Web 应用E序中尽可能多的层次中构建数据验证非帔R要,但是q样做却非常耗时Q以至于很多开发h员都会干脆忽略这个步?—?q可能会D今后大量问题的生。但是随着最新版本的 Java q_中引入了注释Q验证变得简单得多了。在本文中,Ted Bergeron 向您介l如何?Hibernate Annotations ?Validator lg?Web 应用E序中轻松构建ƈl护验证逻辑?/blockquote>有时会有一U工P它可以真正满_发h员和架构师的需求。开发h员在W一ơ下载这U工具当天就可以在自q应用E序中开始用这U工兗理Z来说Q这U工具在开发h员花费大量时间来掌握其用法之前就可以从中L。架构师也很喜欢q种工具Q因为它可以开发h员导向更高理论层ơ的实现。Hibernate Annotations ?Validator lg是一U这L工具?/p>
![]()
开始之前需要了解的内容
在阅L文之前,应该?Java q_版本 5Q尤其是注释Q、JSP 2.0Q因为本文中创徏了一些标{文Ӟq在 TLD 中定义了一些函敎ͼ它们都是 JSP 2.0 的新Ҏ)?Hibernate ?Spring 框架有一个基本的了解。请注意即不?Hibernate 来实现持久性,也可以在自己的应用程序中使用 Hibernate Validator?/p>
Java SE 5 ?Java 语言提供了很多需要的增强功能Q不q其他增强功能可能都不如 注释 q样潜力巨大。?注释Q我们就l于h了一个标准、一U的元数据框架ؓ Java cM用。Hibernate 用户手工~写 *.hbm.xml 文g已经很多q了Q或者?XDoclet 来自动实现这个Q务)。如果手工创Z XML 文gQ那必d每个所需要的持久属性都更新q两个文Ӟcd义和 XML 映射文档Q。?HibernateDoclet 可以化这个过E(请参看清?1 l出的例子)Q但是这需要我们确认自q HibernateDoclet 版本支持要用的 Hibernate 的版本。doclet 信息在运行时也是不可用的Q因为它被编写到?Javadoc 风格的注释中了。Hibernate AnnotationsQ如?2 所C,通过提供一个标准、简明的映射cȝҎ和所d的运行时可用性来对这些方式进行改q?/p>
清单 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 elsewhere public 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; }
如果使用 HibernateDocletQ那么直到生?XML 文g或运行时才能捕获错误。?注释Q在~译时就可以出很多错误Q或者如果在~辑时用了很好?IDEQ那么在~辑时就可以出部分错误。在从头创徏应用E序Ӟ可以利用 hbm2ddl 工具q数据库从 hbm.xml 文g中生?DDL。一些重要的信息 —?比如
name
属性的最大长度必L 60 个字W,或?DDL 应该d非空U束 —?都被?HibernateDoclet Ҏ加到 DDL 中。当使用注释Ӟ我们可以以类似的方式自动生成 DDL?/p>管q两U代码映方式都可以使用Q不q注释的优势更ؓ明显。用注释,可以用一些常量来指定长度或其他倹{编译@环的速度更快Qƈ且不需要生?XML 文g。其中最大的优势是可以访问一些有用信息,例如q行时的非空注释或长度。除了清?2 l出的注释之外,q可以指定一些验证的U束。所包含的部分约束如下:
@Max(value = 100)
@Min(value = 0)
@Past
@Future
在适当条g下,q些注释会引L DDL 生成查约束。(昄Q?code>@Future q不是一个适当的条件。)q可以根据需要创建定制约束注释?/p>
~写验证代码是一个烦Z耗时的过E。通常Q很多开发h员都会放弃在特定的层q行有效性验证,从而可以节省一些时_但是所节省的时间是否能够I补在q个地方因忽略部分功能所引v的缺陷却非常值得探讨。如果在所有应用程序层中创建ƈl护验证所需要的旉可以极大地减,那么争论的焦点就会{向是否要在多个层ơ中q行有效性验证。假设有一个应用程序,它让用户使用一个用户名、密码和信用卡号来创Z个帐受在q个应用E序中所希望q行验证的组件如下:
- 视图Q?/b> 通过 JavaScript q行验证可以避免与服务器反复q行交互Q这样可以提供更好的用户体验。用户可以禁?JavaScriptQ因此这个层ơ的验证最好要有,但是却ƈ不可靠。对所需要的域进行简单的验证是必ȝ?br />
- 控制器: 验证必须在服务器端的逻辑中进行处理。这个层ơ中的代码可以以适合某个特定用途的方式处理验证。例如,在添加新用户Ӟ控制器可以在q行处理之前查指定的用户名是否已l存在?br />
- 服务Q?/b> 相对复杂的业务逻辑验证通常都最适合攑ֈ服务层中。例如,一旦有一个信用卡对象看v来有效,应该用信用卡处理服务对这个信用卡的信息进行确认?br />
- DAOQ?/b> 在数据到达这个层ơ时Q应该已l是有效的了。尽如此,执行一ơ快速检查从而确保所需要的域都非空q且g都在特定的范围或遵@特定的格式(例如 e-mail 地址域就应该包含一个有效的 e-mail 地址Q也是非常有益的。在此处捕获错误L产生可以避免?
SQLException
错误要好?br />- DBMSQ?/b> q是通常可以忽略验证的地斏V即使当前正在构建的应用E序是数据库的惟一客户机,来q可能会d其他客户机。如果应用程序有一?bugQ大部分应用E序都可能会?bugQ,那么无效的数据也可能会被发送给数据库。在q种情况中,如果走运Q就可以扑ֈ无效的数据,q且需要分析这些数据是否可以清除,以及如何清除?br />
- 模型Q?/b> q是q行验证的一个理惛_方,它不需要访问外部服务,也不需要了解持久性数据。例如,某业务逻辑可能会要求用戯提供一个联pM息,q可以是一个电话号码也可以是一?e-mail 地址Q可以用模型层的验证来保用户的确提供了这U信息?
q行验证的一U典型方法是对简单的验证使用 Commons ValidatorQƈ在控制器中编写其他一些验证逻辑。Commons Validator 可以生成 JavaScript 来对视图中的验证q行处理。但?Commons Validator 也有自己的缺P它只能处理简单的验证问题Qƈ且将验证的信息都保存C XML 文g中。Commons Validator 被设计用来与 Struts 一起用,而且没有提供一U简单的Ҏ在应用程序层间重用验证的声明?/p>
在规划有效性验证策略时Q选择在错误发生时单地处理q些错误是远q不够的。一U良好的设计同时q要通过生成一个友好的用户界面来防止出现错误。采用预先进行的Ҏq行验证可以极大地增强用户对于应用程序的理解。不q的是,Commons Validator q没有对此提供支持。假讑ָ?HTML 文g讄文本域的
maxlength
属性来与验证匹配,或者在文本域之后放上一个百分号Q?Q来表示要输入百分比的倹{通常Q这些信息都被硬~写?HTML 文档中了。如果决定修?name
属性来支持 75 个字W,而不?60 个字W,那么需要改动多地方呢Q在很多应用E序中,通常都需要:
- 更新 DDL 来增大数据库列的长度Q通过 HibernateDoclet?hbm.xml ?Hibernate AnnotationsQ?
- 更新 Commons Validator XML 文g最大值增加到 75?
- 更新所有与q个域有关的 HTML 表单Q以修改
maxlength
属性?更好的方法是使用 Hibernate Validator。验证的定义都被通过注释 dC模型层中Q同时还有对所包含的验证处理的支持。如果选择充分利用所有的 HibernateQ这?Validator 可以在 DAO ?DBMS 层也提供验证。在下面l出的样例代码中Q将使用 reflection ?JSP 2.0 标签文g多执行一个步骤,从而充分利用注?囑ֱ动态生成代码。这可以清除在视图中使用的硬~写的业务逻辑?/p>
在清?3 中,
dateOfBirth
被注释ؓNotNull
和过ȝ日期?Hibernate ?DDL 生成代码对这个列d了一个非I约束,以及一个要求日期必L之前日期的检查约束。e-mail 地址也是非空的,必须匚w e-mail 地址的格式。这会生成一个非I约束,但是不会生成匚wq种格式的检查约束?/p>
清单 3. 通过 Hibernate Annotations q行映射的简单联pL?/b>
/** * 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 @Embeddable public 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; }
![]()
样例应用E序
?下蝲 一节,您可以下载一个样例应用程序,它展CZ本文中采用的设计思想和代码。由于这是一个可以工作的应用E序Q因此代码比本文中讨论的的内Ҏ为复杂。例如,清单 9 p选于标签文g text.tagQ这个样例应用程序具有标{文件用的所有代码,以及其他三个cM的标{文件用的代码Q用于选择、隐藏和查框?HTML 元素Q。由于这是一个可以工作的应用E序Q它包含了一个在q种cd的应用程序中都可以找到的架构。还有一?Ant 构徏文g、Spring ?Hibernate XML 装代码Q以?log4j 配置。虽然这些都不是本文介绍的重点,但是您会发现仔细研究一下这个样例应用程序的源代码是非常有用的?如果需要,Hibernate DAO 实现也可以?Validation Annotations。所需做的是在 hibernate.cfg.xml 文g中指定基?Hibernate 事g的验证规则。(更多信息请参?Hibernate Validator 的文档;可以?参考资?/font> 一节中扑ֈ相关的链接)。如果真地希望抄q\Q您可以只捕h务或控制器中?
InvalidStateException
异常Qƈ循环遍历InvalidValue
数组?/p>要执行验证,需要创Z?Hibernate ?
ClassValidator
实例。这个类q行实例化的代h可能会很高,因此最好只对希望进行验证的每个cLq行实例化。一U方法是创徏一个实用工LQ对每个模型对象存储一?ClassValidator
实例Q如清单 4 所C:
清单 4. 处理验证的实用工L
/** * 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
Q一个用?Customer
Q另外一个用?CreditCard
。这两个希望q行验证的类可以调用getInvalidValues(BaseObject modelObject)
Q会q回InvalidValue[]
。这则会q回一个包含模型对象实例错误的数组。另外,q个Ҏ也可以通过提供一个特定的属性名来调用,q样做会只返回与该域有关的错误?/p>在?Spring MVC ?Hibernate Validator ӞZ用卡创徏一个验证过E变得非常简单,如清?5 所C:
清单 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
实例传递给q个验证q程Q从而返?InvalidValue
数组。如果发C一个或多个q种单错误,那么可以将 Hibernate ?InvalidValue
数组转换?Spring ?Errors
对象。如果用户已l创Zq个信用卡ƈ且没有出CQ何简单错误,可以将更加d的验证委托给服务层进行。这一层可以与商业服务提供者一起对信用卡进行验证?/p>现在我们已经看到q个单的模型层注释是如何q到控制器、DAO ?DBMS 层的验证的。在 HibernateDoclet ?Commons Validator 中发现的验证逻辑的重合现在都已经l一到模型中了。尽这是一个非常受Ƣ迎的改q,但是视图层传l上来说一直是最需要进行详l验证的地方?/p>
![]()
![]()
![]()
![]()
回页?/font>
在下面的例子中,使用?Spring MVC ?JSP 2.0 标签文g。JSP 2.0 允许?TLD 文g中对定制函数q行注册Qƈ在一个标{文件中q行调用。标{文件类g taglibsQ但是它们是使用 JSP 代码~写的,而不是?Java 代码~写的。采用这U方法,使用 Java 语言写好的代码就可以装成函敎ͼ而?JSP 写好的代码则可以攑օ标签文g中。在q种情况中,Ҏ释的处理需要用映像,q会由几个函数来执行。绑?Spring 或呈?XHTML 的代码也是标{文件的一部分?/p>
清单 6 中节选的 TLD 代码定义 text.tag 文g可以使用Qƈ定义了一个名?
required
的函数?/p>
清单 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
c,其中包含了标{文件用的所有函数。在前文中我们曾l说q,最适合使用 Java 代码~写的代码都被放C几个 TLD 可以映射的函CQ这h{文件就可以使用它们了;q些函数都是?Utilities
cMq行~码的。因此,我们需要三样东西:定义q些cȝ TLD 文g?code>Utilities 中的函数Q以及标{文件本w,后者要使用q些函数。(W四样应该是使用q个标签文g?JSP 面。)在清?7 中,l出了在 TLD 中引用的函数和另外一个表C给定属性是否是
Date
的方法。在q个cM要涉及到比较多的代码Q但是本文限于篇q,不会l出所有的代码Q不q需要注?findGetterMethod()
除了表辑ּ语言QExpression LanguageQELQ方法表C(customer.contact
Q{换成 Java 表示Q?code>customer.getContact()Q之外,q执行了基本的映像操作?/p>
清单 7. Utilities 节?/b>
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(); } }
此处可以清楚地看到在q行时?Validation annotations 是多么容易。可以简单地引用对象?getter ҎQƈ查这个方法是否有相关的给定的注释 ?/p>
清单 8 中给出的 JSP 例子q行了简化,q样可以着重查看相关的部分了。此处,q里有一个表单,它有一个选择框和两个输入域。所有这些域都是通过?form.tld 文g中声明的标签文gq行呈现的。标{文件被设计成用智能缺省|q样可以根据需要允许简单编码的 JSP 可以有定义更多信息的选项。关键的属性是
propertyPath
Q它使用 EL W号这个域映射为模型层属性,像是?Spring MVC ?bind
标签一栗?/p>
清单 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 文g的完整源代码太大了,不好攑֜q儿Q因此清?9 l出了其中关键的部分Q?/p>
清单 9. 标签文g text.tag 节?/b>
<%@ 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
是惟一需要的属性?code>size?maxlength
?required
都可以忽略?code>objectPath var 被设|ؓ?propertyPath
中引用的属性的父对象。因此,如果propertyPath
?customer.contact.fax.number
Q?那么objectPath
应该被讄?customer.contact.fax
。我们现在就使用 Spring ?bind
标签l定C包含属性的对象上。这会将对象变量讄成对包含属性的实例的引用。接下来Q检查这个标{用户是否已经指定?她们是否希望属性是必须的。允许表单开发h员覆盖从注释中返回的值是非常重要的,因ؓ有时?她们希望让控制器为所需要的域设|缺省|而用户可能ƈ不希望ؓq个域提供倹{如果表单开发h员没有ؓrequired
指定|那么可以调用这个表?TLD ?required
函数。这个函数调用了?TLD 文g中映的Ҏ。这个方法简单地?@NotNull
注释Q如果它发现某个属性具有这个注释,将labelClass
变量讄为必ȝ。可以类似地定正确?maxlength
以及q个域是否是一?Date
?/p>接下来?Spring 来绑定到
propertyPath
上,而不是像前面一样只l定到包含这个属性的对象上。这允许在生?label
?input
HTML 标签时?status.expression
?status.value
?input
标签也可以用一个大?maxlength
以及适当的类来生成。如果前面已l确定属性是一?Date
Q现在就可以d JavaScript 日历了。(可以?参考资?/font> 一节找C个很好的日历lg的链接)。注意根据需要链接属性、输?ID 和图?ID 的标{是多么单。)q个 JavaScript 日历需要一个图?ID 来匹配输入域Q其后缀?_button
?/p>最后,可以?
<jsp:doBody/>
装C?span
标签中,q样允许表单开发h员在面中添加其他图标,例如用来L帮助的图标。(清单 8 l出了一个ؓ信用卡号域添加的帮助图标。)最后的部分是检?Spring 是否个属性报告和昄了一个错误,q和一个错误图标一hC?/p>使用 CSSQ就可以对必ȝ域进行一下装?—?例如Q让它们以红色显C、在文本边上昄一个星P或者用一个背景图像来装饰它。在清单 10 中,必ȝ域的标签讄成黑Ԍ而且后面昄一个红色的星号Q在 Firefox 以及其他标准兼容的浏览器中)Q如果是?IE 中则q会在左边加上一个小旗子的背景图像:
清单 10. 对必dq行装饰?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
属性可以防止用戯入太多文本所引v的错误。可以扩?text
标签来ؓ输入域类讄其他的数据类型。可以修?text
标签使用 HTMLQ而不?XHTMLQ如果希望这P。可以不太费力地获得h正确语义?HTML 表单Q而且不需学习Zlg的框架知识,可以利用基于组件的 Web 框架的优炏V?/p>管标签文g生成?HTML 文g可以帮助防止一些错误的产生Q但是在视图层ƈ没有M代码来真正进行错误检查。由于可以用类属性,现在可以添加一些简单的 JavaScript 来实现这U功能了Q如清单 11 所C。这里的 JavaScript 也可以是通用的,在Q一表单中都可以重用?/p>
清单 11. 单的 JavaScript 验证E序
<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 text var labelFor = requiredLabels[i].getAttribute("for"); // Grab the for attribute var 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>
q个 JavaScript 是通过单声明添?
onsubmit="return checkRequired(this);"
被调用的。这个脚本简单地获取h所需要的cȝ表单中的所有元素。由于我们的习惯是在标签标记中用这个类Q因此代码会通过for
属性来查找与这个标{连接在一L输入域。如果Q何输入域为空Q就会生成一条简单的警告消息Q表单提交就会取消。可以简单地对这个脚本进行扩充,使其扫描多个c,q相应地q行验证?/p>对于Z JavaScript 的综合的验证集合来说Q最好是使用开源实玎ͼ而不是自行开发。在清单 8 中您可能已经注意C面的代码Q?/p>
onclick="new Effect.SlideDown('creditCardHelp')"
q个函数?Script.aculo.us 库的一部分Q这个库提供了很多高U的效果。如果正在?Script.aculo.usQ就需要对所构徏的内容?Prototype 库?JavaScript 验证库的一个例子是?Andrew Tetlaw ?Prototype 基础上构建的。(请参?参考资?/font> 一节中的链接。)他的框架依赖于被d到输入域的类Q?/p>
<input class="required validate-number" id="field1" name="field1" />
可以单地修改 text.tag 的逻辑?
input
标签中插入几个类。将class="required"
d到输入标{֒label
标签中不会媄?CSS 规则Q但会破坏清?10 中给出的?JavaScript 验证E序。如果要混合使用框架中的代码和简单的 JavaScript 代码Q最好用不同的cdQ或在用类名搜索元素时保cd有效q检查标{型?/p>
![]()
![]()
![]()
![]()
回页?/font>
本文已经介绍了模型层的注释如何充分用来在视图、控制器、DAO ?DBMS 层中创徏验证。必L工创建服务层的验证,例如信用卡验证。其他模型层的验证,例如强制属?C 是必ȝQ而属?A ?B 都处于指定的状态,q也是一个手工Q务。然而,使用 Hibernate Annotations ?Validator lgQ就可以L地声明ƈ应用大多数验证?/p>
不论是简单例子还是所引用框架?JavaScript 验证都可以对单的条gq行查,例如域必要填写Q或者客h端代码中的数据类型必要匚w预期的类型。需要用到服务器端逻辑的验证可以?Ajax d?JavaScript 验证E序中。您可以使用一个用h册界面来让用户可以选择用户名。文本标{֏以进行增强来?
@Column(unique = true)
注释。在扑ֈq个注释Ӟ标签可以d一个用来触?Ajax 调用的类?/p>现在您不需要在应用E序层间l护重复的验证逻辑了,q样可以节省出大量的开发时间。想像一下您最l可以ؓ应用E序所能添加的增强功能Q?/p>
![]()
![]()
![]()
![]()
回页?/font>
描述 名字 大小 下蝲Ҏ CZ应用E序 j-hibval-source.zip 8MB HTTP
![]()
![]()
关于下蝲Ҏ的信?/font> ![]()
![]()
Get Adobe] Reader]
学习
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文 ?br />
- XDoclet @hibernate 标签参?/font> Q学习更多有?XDoclet ?HibernateDoclet 的内宏V?
- Hibernate Validator 文档 Q学?Hibernate Validator 的细节?
- Really easy field validation with Prototype Q引?Andrew Tetlaw 的博客?
- Java 技术专?/font> Q?q里有数癄关于 Java ~程各个斚w的文章?br />
获得产品和技?/b>
- Hibernate ?SpringQ这些框架构成了本文例子的基?br />
- Script.aculo.us Q用?Web 2.0 应用E序?JavaScript?br />
- Prototype Q了解这?JavaScript 框架?br />
- DHTML / JavaScript 日历 Q将光成到自己的项目中?br />
- Silk Icons Q这些免费的图标非常适合?Web 表单中标识域?br />
讨论
- developerWorks blogs Q加?developerWorks C?
![]()
![]()
![]()
Ted Bergeron ?Triview 的合作创始h之一QTriview 是一家企业Y件咨询公司,位于加利尼亚的圣地亚哥。Ted 从事Z Web 的应用程序的设计已经有十 多年的时间了。他所做过的一些知名的目包括?Sybase、Orbitz、Disney ?Qualcomm 所设计的项目。Ted q曾用三 q的旉作ؓ一名技术讲师来教授有关 Web 开发、Java 开发和数据库逻辑设计的课E。您可以?Triview ?Web 站点 上了解有?Triview 公司的更多内容,或者也可以拨打该公司的免费电话 (866)TRIVIEW?/p>
]]>