基於Hibernate校驗器組件實現Web開發中的校驗邏輯

一、簡介
雖然在Web應用程序的多個層內加入相應的數據校驗功能都十分必要,但是以前實現這項任務是相當費時的,這使得許多開發者乾脆略過它—這明顯會帶來 一系列的問題。但是,隨着在最新版本的Java平臺中引入了註解概念,校驗問題容易得多了。在本文中,我將向你展示在你的Web應用程序中使用 Hibernate註解中的校驗器(Validator)組件來構建和維護校驗邏輯是多麼容易的事情。
二、預備知識
閱讀本文前,你應該對下列內容有一個基本瞭解:Java 5.0(具體地說是其中的“註解”概念);JSP 2.0(因爲我在一個TLD中創建標籤文件和定義函數,它們都是JSP 2.0新引入的特徵);還有Hibernate和Spring框架。另外請注意,即使沒有使用Hibernate實現持久性存儲,你也可以在自己的應用程 序中使用Hibernate Validator。
與以前的版本相比,Java SE 5增加了許多改進,也許再也沒有比註解更爲重要的了。藉助於註解,你可以最終爲你的Java類構造出一種標準的、一流的元數據框架。多年以來, Hibernate用戶一直是手工地編寫*.hbm.xml文件(或使用XDoclet自動完成這一任務)。如果你手工地創建XML文件的話,相應於每一 種需要的持久性屬性,你都必須更新兩個文件(類定義和XML映射文檔)。儘管使用HibernateDoclet可以簡化這一工作(具體示例請參考列表 1),但是要求你確保你的HibernateDoclet版本支持你想使用的Hibernate版本。另一方面,這種doclet信息在運行時刻還不可 用,因爲它被編碼成Javadoc風格的註釋。列表2中的Hibernate註解對這些選擇進行了改進—提供一個標準的簡明的映射類,而且在運行時刻可 用。
列表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註解創建的Hibernate映射代碼
@NotNull
@Column(name = "name")
@Length(min = 1, max = NAME_LENGTH) //NAME_LENGTH是一個在另外地方聲明的常數
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;
}
如果你使用HibernateDoclet,那麼,只有到生成XML文件或在運行時刻你才能捕獲錯誤。藉助於註解,你能夠檢測編譯時刻中的許多錯 誤;或者,如果你使用的是一種優秀的IDE的話,在編輯期間也能夠檢測出許多錯誤。當從頭創建一個應用程序時,你可以利用hbm2ddl工具來由 hbm.xml文件爲你的數據庫生成DDL。特別注意:name屬性的最大長度不超過60個字符,也就是說,DDL應該添加一個“not null”型約束—從HibernateDoclet入口添加到DDL中。當你使用註解時,你可以用一種與此類似的方式自動地生成DDL。
儘管上面列舉的兩種代碼映射方案都可以使用;但是,相比之下,註解具有更爲清晰的優點。藉助於註解,你可以使用約束來指定長度或其它值。你將擁有更 快的構建週期而不需要生成XML文件。最大優點是,你能夠在運行時刻存取有用的信息,例如一個“not null”註解或長度。除了在列表2展示的註解之外,你還可以指定校驗約束。其中,一些非常有用的約束列舉如下:
在適當的情況下,這些註解將會導致可以使用DDL生成檢查約束(顯然,@Future並不是一種合適的情形)。你還能夠據自己的需要創建定製的約束註解。
三、校驗與應用程序層
編寫校驗代碼可能是一個十分冗長而且相當耗時的過程。所以,很多情況下,開發的主要負責人都會放棄在一特定層實現校驗以便節約時間,但是這常常引起 爭議—是否這樣做所導致的缺點會抵銷所節約的時間?如果花費在整個應用程序層創建和維護校驗方面的時間能夠極大地減少,那麼,我們更應該推薦在更多的層中 實現校驗功能。假定你有一個允許用戶通過用戶名、口令和信用卡號來創建帳戶的應用程序,那麼,你很可能喜歡在如下一些應用程序組件上加入校驗邏輯:
◆視圖:通過JavaScript進行校驗有望避免客戶端與服務器的來回“折騰”,並且能夠提供一種更好的用戶體驗。但是,由於用戶有可能會禁用JavaScript,因此儘管這一級別的校驗實在不錯,但是卻並不可靠。但無論如何,對要求的域進行簡單的校驗是必需的。
◆控制器:校驗必須按照服務器端邏輯進行處理。在這一層的代碼可以以一種適當的方式來處理校驗以適用於特定場所。例如,當添加一個新用戶時,在繼續操作之前,控制器可以首先檢查是否該指定的用戶名已經存在。
◆服務:相對複雜的業務邏輯校驗最經常情況下應該放於這個服務層來實現。例如,一旦你有一個看上去似乎有效的信用卡對象,那麼你就應該使用你的校驗處理服務來驗證該信用卡信息。
◆DAO: 在數據到達這一層時,它確實應該是有效的。即使如此,再執行一次快速的檢查以確保要求的域非空而且該值滿足指定的範圍或遵循指定的格式化(例如,一個電子 郵件地址域應該包含一個有效的電子郵件地址)都是非常有益的。在這一層捕獲一個問題要比導致系統拋出SQLException異常(實際上能夠避免)要好 得多。
◆DBMS:對這一部分的校驗常常被忽視。即使你今天構建的應用程序僅是數據庫的客戶端,其它客戶端也有可能在未來某一天添加進來。如果應 用程序存在錯誤(並且大多數情況下是如此),那麼,無效的數據就會到達數據庫中。在這種情況下,如果你幸運的話,你有可能會發現這部分無效的數據並要求你 確定是否以及怎樣把它清理掉。
◆模型:在這一層比較適合於加入對不要求存取外部服務或持久性數據信息的校驗。例如,你的業務邏輯可以指示用戶至少提供一種形式的聯繫人信息,或者是一個電話號碼或者是一個電子郵件地址,那麼,此時你可以使用模型層校驗來確保用戶實現滿足這一要求。
一種典型的校驗方案是:首先使用Commons Validator實現簡單校驗,然後在控制器中編寫其它的校驗。Commons Validator提供了自動生成JavaScript來處理視圖中校驗功能的優點。但是,Commons Validator也確實存在它自己的不足:它僅能處理簡單的校驗並且它把校驗定義存儲到一個XML文件內。Commons Validator的設計目的是爲了與Struts聯用,因此沒有提供一種容易的方法來實現跨整個應用程序層重用校驗聲明。
當規劃你的校驗策略時,僅僅簡單地選擇在發生錯誤時才預以處理是遠遠不夠的。一個良好的設計還應該能夠防止錯誤-通過生成一種友好的用戶接口。採取 積極的校驗方法能夠極大地增強用戶使用應用程序的感覺。遺憾的是,Commons Validator沒有提供這種支持。假定你想使你的HTML設置文本域的maxlength屬性以匹配校驗或是在文本域後面放置一個百分號(%)用以提 示要收集一個百分比值—典型地,這種信息往往被硬編碼到HTML文件中。如果你決定改變你的name屬性以支持75個字符長度而不是60個字符,那麼,你 需要在多少地方加入你的這一改變呢?在許多應用程序中,你將需要:
◆更新DDL以提高數據庫中數據表格的列長度(通過HibernateDoclet,hbm.xml或Hibernate註解)。
◆更新Commons Validator XML文件,從而把最大值提高爲75。
◆更新所有的與這個域有關的HTML表單來修改maxlength屬性。
一種更好的方法是使用Hibernate Validator。可以通過註解把校驗定義添加到模型層,同時包括對處理校驗的支持。如果你選擇全部利用Hibernate,那麼,校驗器會有助於在 DAO和DBMS層提供校驗。在接下來的代碼示例中,你將通過使用反射和JSP 2.0標籤文件把這種情況更深入一層—利用註解動態地生成視圖層代碼。這樣以來,就不必要再在視圖中進行業務邏輯硬編碼。
在列表3中,dateOfBirth被註解爲NotNull並且是已經過去的日期。Hibernate的DDL生成將把一個“not null”約束添加到這一列,還將加入一個要求該日期必須是已經過去的日期的檢查約束。此外,電子郵件地址也必須是“not null”並且必須匹配電子郵件格式。這將生成一個“not null”約束但是卻不生成一個匹配該格式的檢查約束。
列表3.經由Hibernate註解實現的聯繫人信息映射
/**
*一個簡化的存儲聯繫人信息的對象
*/
@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;
}
四、示例應用程序
本文提供了一個完整的可工作的示例應用程序(代碼相當長)。其中涉及到多方面的內容,例如,多個標籤文件(列表9中僅涉及到一個),還包括一個完整 的可工作的應用程序框架;還提供了一個Ant構建文件,Spring和Hibernate XML綁定代碼和一個log4j配置文件。儘管這些都不是本文討論的重點,但是你會發現本文所提供的示例程序源代碼相當有用。
如果需要的話,你也可以使用Validation註解來實現Hibernate DAO。爲此,你僅需要在hibernate.cfg.xml文件中指定基於Hibernate事件的校驗即可。如果你編碼相當嚴格,那麼你可以在服務或 控制器層捕獲InvalidStateException異常並遍歷InvalidValue數組。
五、在控制器中加入校驗功能
爲了執行校驗,你需要創建Hibernate的ClassValidator的一個實例。要實例化這個類需要較高的代價,所以,最好僅對你確定需要 校驗的類實施這一操作。一種方法是,針對每一個模型對象創建一個工具類或存儲一個ClassValidator實例和singleton,如列表4所示:
列表4.處理校驗的工具類
/**
*基於Hibernate Annotations Validator框架進行校驗
*/
public class AnnotationValidator {
private static Log log = LogFactory.getLog(AnnotationValidator.class);
//執行下面幾行並對校驗器實例進行緩衝是一種良好的編碼習慣
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 {
//僅取得指定屬性的無效值。
//例如,“city”應用於getCity()方法
validationMessages = validator.getInvalidValues(modelObject, property);
}
return validationMessages;
}
}
在列表4中,你創建了兩個ClassValidators:一個用於Customer校驗,另一個用於CreditCard校驗。要進行校驗的類可 以調用getInvalidValues(BaseObjectmodelObject)—它的返回值爲InvalidValue[];這是一個包含模型 對象實例錯誤信息的數組。作爲選擇,你可以使用一個特定的屬性名來調用這個方法;此時,它僅返回相應於該域的錯誤信息。
當使用Spring MVC和Hibernate Validator時,創建一個信用卡校驗器已經非常簡單了,詳見列表5。
列表5.由Spring MVC控制器所使用的CreditCardValidator
/**
*在Spring MVC實現信用卡校驗
*/
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);
//僅在上面沒有出現簡單錯誤時才進行“昂貴”的校驗
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文件內登記並在一個標籤文件中進行調用。標籤文件類似於taglib庫,但它是以JSP代碼而不是以Java代碼寫成的。 通過這種方式,使用Java語言編寫的代碼可以被封裝於函數中,而使用JSP編寫的代碼可以被放於標籤文件內。在我們的例子中,處理註解要求使用反射功 能,這將通過若干函數來實現。綁定到Spring或生成XHTML的代碼將成爲標籤文件的一部分。
列表6中的TLD摘錄定義了一個我們要使用的text.tag文件和一個稱爲“required”的函數:
列表6.創建表單TLD
<?xml version="1.0" encoding="ISO-8859-1" ?>
<taglib xmlns="[url]http://java.sun.com/xml/ns/j2ee[/url]"
xmlns:xsi="[url]http://www.w3.org/2001/XMLSchema-instance[/url]"
xsi:schemaLocation="[url]http://java.sun.com/xml/ns/j2ee[/url]
[url]http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd[/url]"
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是工具類中的部分代碼,其中包含了爲標籤文件所使用的所有函數。在前面,我已經提到把最好以Java代碼方式編寫的代碼放於若干函數中;然 後,由TLD對它們進行映射以便進一步爲標籤文件所使用;所有這些函數都位於這個工具類內。因此,你需要三部分:TLD文件定義一切;在工具中定義函數; 使用這些函數的標籤文件(第四部分是使用該標籤文件的JSP頁面)。
在列表7中,你定義了爲TLD所引用的required函數和另一個指示一個給定屬性是否是一個日期的方法。其實,這個工具類中還有大量內容,但篇 幅所限,在此不贅述。但是請注意,findGetterMethod()及其它方法都要執行基本的反射—除了實現從表達式語言(EL)方法表示 (customer.contact)轉換成Java表示(customer.getContact())以外。
列表7.工具類中的主要代碼片斷
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();
}
}
在此,我們看到在運行時刻使用校驗註解是非常容易的。你只需簡單地獲取到一個對象的getter方法的引用並檢查是否該方法有一個與之相關聯的給定註解即可。
列表8中的JSP示例被大大簡化;這樣你會專注於相關的部分。其中建立了一個帶有一個選擇框和兩個輸入域的表單—所有這些域都是通過在 form.tld文件中聲明的標籤文件生成的。這些標籤文件的設計目標是爲了使用缺省值—這是考慮到在簡單編碼的JSP中可以根據要求指定更多的信息。這 裏的關鍵屬性是propertyPath,它使用EL標誌把域映射到相應的模型層屬性,就象使用Spring MVC的綁定標籤實現的效果一樣。
列表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"
Effect.SlideDown('creditCardHelp')"/>
</form:text>
<form:text propertyPath="creditCard.expirationDate"/>
</form>
列表9中僅列出text.tag文件的關鍵部分(完整內容請參考本文下載源碼文件):
列表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="[url]http://www.springframework.org/tags[/url]" 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
ifFormat : "%m/%d/%Y", //日期格式
button : "${status.expression}_button" //按鈕的ID
}
);
</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)可以選擇性地指定。這個 objectPath變量被置爲在propertyPath中引用的屬性的父對象。因此,如果propertyPath是 customer.contact.fax.number,那麼,objectPath將被置爲customer.contact.fax。現在,你需要 使用Spring的綁定標籤來綁定包含你的屬性的對象。這將把你的對象變量置爲一個對包含你的屬性的實例的引用。接下來,你需要檢查以確定這個標籤的用戶 是否已經指定他們要求使用你的屬性。允許表單開發者重載從註解返回的值是十分重要的;這樣以來,他們可以使控制器設置要求的域的缺省值(用戶可能不想提供 該域的值)。如果表單開發者沒有指定一個要求的值,那麼你可以調用TLD形式的required函數;這個函數再去調用在TLD文件中映射的相應方法。此 方法僅執行@NotNull註解檢查;如果它發現屬性有這個註解,那麼它會把labelClass變量按要求賦值。此外,你可以用一種類似方式來決定適當 的maxlength以及是否該域爲一個日期。
接下來,你要使用Spring綁定到propertyPath,這與你前面所使用的僅包含屬性的對象形成對照。這允許你在生成label和 input HTML標記時使用status.expression和status.value。這個input標籤還可以根據一個給定的size, maxlength和class而生成。如果你早些時候決定你的屬性爲一個Date;那麼現在,你可以加入一個JavaScript日曆。請注意,把標籤 的屬性、輸入ID和圖像ID鏈接到一起是非常簡單的。這個JavaScript日曆僅要求圖像ID匹配input域(而且添加上一個 “_button”)。
最後,你使用一個span標籤來包裝<jsp:doBody/>,從而允許表單開發者添加其它圖標,例如把一個幫助圖標添加到頁面上 (列表8展示了一個幫助圖標被添加到信用卡號域)。上面最後一部分代碼檢查是否Spring已經打印一個針對這個屬性的錯誤並預以顯示(同時還顯示一個錯 誤圖標)。
藉助於一點點CSS編碼,你就可以輕鬆修飾你的表單域—例如,讓它們以紅色顯示,或者在它們的文本之後添加一個星號,或者加上一個背景圖像。在列表 10中,對於Firefox及其它標準兼容的瀏覽器的情況,你把相應的表單域的標籤設置爲黑色,後面跟着一個紅色的星號;而對於Internet Explorer的情況,則是把一個標誌背景圖像添加到Internet Explorer的左邊:
列表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標籤以設置input域的class,用於輸入其它類型的數據。你還能夠修改text標籤以使用HTML來代替XHTML (如果你喜歡這樣做的話)。這樣以來,你能夠不費勁地實現優秀的HTML表單所具有的優點,並且你可以實現一個基於組件的Web框架中的許多優點而不必去 專門學習一種基於組件的框架。
儘管標籤文件能夠生成有助於防止錯誤的HTML,但是,你在視圖層卻沒有使用任何代碼來完成實際的錯誤檢查。而藉助於class屬性,你可以添加一些簡單的JavaScript來實現這一點,見列表11。
列表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; //取得標籤的文本
var labelFor = requiredLabels[i].getAttribute("for"); //取得“for”屬性
var inputTag = document.getElementById(labelFor); //得到輸入標籤
if (inputTag.value == null || inputTag.value == "") {
alert("Please make sure all required fields have been entered.");
return false;//取消提交
}
}
return true;
}
</script>
通過在表單聲明中添加onsubmit="return checkRequired(this);"語句調用這一段JavaScript代碼。這段腳本簡單地提取滿足要求類型的所有元素。由於你是在label 標籤中使用這個類,所以後面的代碼通過它的for屬性查找與之相聯繫的input域。如果這些input域中的任何一個爲空,那麼,將生成一個簡單的警告 消息並且放棄表單提交。另外,你還可以容易地擴展這個腳本來掃描若干種類型(class)並實現相應的校驗。
最後,我建議:要實現一組全面的基於JavaScript的校驗功能,最好使用一個開源框架而不是一切由你自己來實現。請看下面列表8中的代碼:
Effect.SlideDown('creditCardHelp')"
這個函數是優秀的Script.aculo.us庫(這個庫實現了許多高級效果)中的一部分。如果你正在使用這個Script.aculo.us庫,那麼,你還會用到Prototype庫(Script.aculo.us庫正是基於它構建而成)。
七、總結
本文中,我們分析瞭如何把你的模型層中的註解用於創建你的視圖、控制器、DAO及DBMS層中的校驗。注意,你必須手工地創建服務層校驗(例如信用 卡校驗)。其它的模型層校驗,例如當屬性A和B處於特定狀態時要求強制實現屬性C;這樣的情況仍然需要手工去實現。然而,藉助於Hibernate註解中 的Validator組件,你可以很容易地聲明和實現大部分的校驗任務。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章