摘要:
本文根據(jù)BJUG maillist討論整理,取自Buffalo網(wǎng)站,在此對Michael的貢獻表示感謝!文中引用的評論僅表示個人觀點,供大家作為技術(shù)參考。先敬佩Michael一個,能做到這樣,Michael付出了很多。下文簡單比較一下Buffalo和DWR,兩者的思路基本類似,有很多共性的東西。總的看來,Buffalo能滿足基本的需要,但DWR已經(jīng)在整體系統(tǒng)結(jié)構(gòu)上有了更多優(yōu)勢...
Buffalo VS DWR
作者:cleverpig
聲明:文中引用的評論僅表示個人觀點,供大家作為技術(shù)參考。
開篇言:
????本文根據(jù)
BJUG maillist討論整理,取自
Buffalo網(wǎng)站,在此對Michael的貢獻表示感謝!文中引用的評論僅表示個人觀點,供大家作為技術(shù)參考。先敬佩Michael一個,能做到這樣,Michael付出了很多。下文簡單比較一下Buffalo和DWR,兩者的思路基本類似,有很多共性的東西。總的看來,Buffalo能滿足基本的需要,但DWR已經(jīng)在整體系統(tǒng)結(jié)構(gòu)上有了更多優(yōu)勢。框架的生命周期是有限的,如果不能與時俱進,將遲早面臨被淘汰的局面。
版權(quán)聲明:任何獲得Matrix授權(quán)的網(wǎng)站,轉(zhuǎn)載時請務(wù)必保留以下作者信息和鏈接作者:cleverpig;
cleverpig原文:
http://www.matrix.org.cn/resource/article/2006-10-18/Buffalo+DWR_4ebd1e01-5e90-11db-a5c2-7f23a8944cb0.html關(guān)鍵字:buffalo,dwr,ajax,比較
一、兩個Ajax框架:
????
Buffalo是一個為java web項目而設(shè)計的輕量級AJAX框架。它是開源的(Apache License 2.0),其功能強大且易用。主要解決在j2ee ajax開發(fā)中的常見問題。它承諾為開發(fā)者和最終用戶都能受益。最重要的Buffalo的作者Michael Chen是位中國開發(fā)者,他就生活在北京。

????
DWR是一個Java開源庫,幫助你實現(xiàn)Ajax網(wǎng)站。它可以讓你在瀏覽器中的Javascript代碼調(diào)用Web服務(wù)器上的Java,就像在Java代碼就在瀏覽器中一樣。DWR會根據(jù)你的Java類動態(tài)的生成Javascript代碼。這些代碼的魔力是讓你感覺整個Ajax調(diào)用都是在瀏覽器上發(fā)生的,但事實上是服務(wù)器執(zhí)行了這些代碼,DWR負責數(shù)據(jù)的傳遞和轉(zhuǎn)換。
二、共性:????
支持Spring集成:均支持。
????
使用一個Servlet來接收所有的AJAX請求:????????Buffalo使用ApplicationServlet<————>DWR使用DWRServlet。
????
使用XMLHttpRequest + JavaScript 傳輸數(shù)據(jù):????????Buffalo使用buffalo.js<————>DWR使用dwr.js & util.js。
????
在Java和Javascript之間轉(zhuǎn)換數(shù)據(jù):????????Buffalo使用marshallingContext<————>DWR使用Converter。
????
協(xié)議:????????Buffalo在1.2.x之前采用burlap, Buffalo解析大數(shù)據(jù)量可能會比較慢,然而可以適用于多種服務(wù)器端和客戶端,并且burlap協(xié)議的完整性和支持的數(shù)據(jù)類型更加豐富。2.0開始采用自定義的基于xml的協(xié)議(來自burlap,做了更適合web的修改),并自行編寫了解析器,性能更高。
????????DWR使用自定義的簡單文本協(xié)議。
三、Buffalo特性:??
1. 基于prototype:如果你的AJAX應(yīng)用也是基于prototype,那么可以減少重復(fù)加載prototype的帶寬,并且獲得相當一致的編程概念。
??
2. Bind:提供了對結(jié)果數(shù)據(jù)的處理,直接將數(shù)據(jù)綁定到頁面對象并展示,這是一個動人的特性。(DWR在Util.js中也提供了一些方法來簡化數(shù)據(jù)的展示,但不如 Buffalo做的更多。) 在2.0中,Bind能力更加強大,能夠?qū)⒅抵苯咏壎ǖ奖韱卧亍⒈砀瘛IV/Span、甚至整個表單上。關(guān)鍵是這種綁定是無侵入并且與buffalo 整體結(jié)構(gòu)完全整合,對外表現(xiàn)只有一個簡單的buffalo.bindReply或者Buffalo.Bind.bind即可。http://buffalo.sourceforge.net/binding.html有一些描述。
??
3. 序列化:Buffalo支持任意對象,任意深度,任意數(shù)據(jù)結(jié)構(gòu)的java到j(luò)avascript以及javascript到j(luò)ava的雙向序列化。并且支持引用。這里有完整的協(xié)議說明。由于文檔和演示不充分,很多人以為buffalo不支持任意對象了 ~
??
4. 生命周期對象訪問:1.2.4之前需要繼承一個BuffaloService,
????從1.2.4開始就不需要繼承了,引入了線程安全的BuffaloContext對象,只需要通過BuffaloContext.getContext()即可獲得一個線程安全的引用,并且對Request的各種屬性進行操作。更方便的是:
??????Map BuffaloContext.getContext.getSession()
??????Map BuffaloContext.getContext.getApplication()
??????Map BuffaloContext.getContext.getCookie()
??????即可獲得session/application/cookie的Map,操作這些Map即可獲得對這些生命周期的各種變量進行查詢和更新。這個特性參考了webwork中ActionContext的設(shè)計。
??
5. 對Collection/Array的模糊處理:buffalo中提供了對Collection/Array對象的模糊識別能力。例如:服務(wù)器端有一個方法需要List參數(shù),客戶端傳遞過去一個javascript數(shù)組就可以了,不需要費心的組裝對象。buffalo通過這些很細小的地方來提高程序員生產(chǎn)力。
??
6. 客戶端組裝對象:據(jù)報告DWR只支持簡單數(shù)據(jù)類型作為參數(shù)在客戶端傳入。buffalo支持在客戶端組裝對象,甚至可以直接將整個表單序列化為一個對象作為參數(shù)傳給遠程客戶端。DWR協(xié)議天生不足,這方面,我猜想它完全沒有能力。
??
7. 對重載方法的處理能力:由于java與javascript之間類型的不匹配,DWR的代碼生成無法對重載方法進行處理。例如,sum(double,double), sum(int, int) DWR很可能不知道你要調(diào)用哪一個。從2.0開始buffalo支持了對重載的處理。
四、DWR特性:??
1. 支持Batch:可以將多個Service函數(shù)調(diào)用放在一個XMLHttpRequest請求中完成。
?? Michael Chen評論:我一直認為這不是一個好的實踐。在客戶端發(fā)起多個請求并獲得響應(yīng)除了獲得編程的復(fù)雜外,還增加了服務(wù)器端設(shè)計service的自由度。這種方式感覺上更鼓勵為遠程調(diào)用設(shè)計細粒度的服務(wù)、將組裝邏輯放在客戶端。這種設(shè)計風格我不太喜歡,因此batch也一直沒有考慮實現(xiàn),雖然實現(xiàn)不太麻煩。
??
2. Converter:可以轉(zhuǎn)換任意類型的Java對象到JavaScript,并允許直接使用。例如:Customer類包含一個address變量,當AjaxCall返回Customer對象的時候,可以直接在Javascript中使用customer.address來獲得Address的信息。
??
3. HttpServlet:支持在被調(diào)用的Service方法中獲得HttpServletRequest和HttpServletResponse對象,這樣可以訪問當前Session中的數(shù)據(jù)。
??
4. 允許Expose部分函數(shù)和屬性。(Buffalo無限制,可以訪問Service中的任意函數(shù)。)
?? Michael Chen評論: 這個我也考慮過...DWR的代碼生成機制使得它不得不通過這種方式減小些流量。Buffalo如果想實現(xiàn)這個特性也不是不行,只是我覺得,既然 Service辛辛苦苦實現(xiàn)了,還需要通過這種方式來讓別人不能用么?況且buffalo沒有代碼生成,無論你暴露多少方法流量都是一樣的。考慮到實際情況,buffalo沒有實現(xiàn)這個特性。
??
5. DWR2.0中提出了Reverse Ajax:提供在Java代碼中來處理頁面上元素的功能。
?? Michael Chen評論: 這個東東...也還是代碼生成的trick...然而我的態(tài)度是javascript與java同樣重要的,因此不會讓代碼生成類的東西破壞javascript的整體性。
五、相關(guān)資源:
?? 在過去的兩年間,baffalo的開發(fā)者擁有值得興奮的積極反饋:buffalo非常適于在java web項目的ajax開發(fā)。為了幫助更多的用戶更好地使用buffalo,他們期待你的幫助:發(fā)布文檔、bug報告和bug修正等。
?? baffalo Maillist:
buffalo-users@lists.sourceforge.net?? baffalo 論壇:
http://groups.google.com/group/amowa
cleverpig寫的Buffalo的最佳實踐
posted @
2006-10-19 15:31 xzc 閱讀(428) |
評論 (0) |
編輯 收藏
摘要: 在上一篇文章《為Struts 2.0做好準備》中,我過于詳細地介紹了Struts 2.0開發(fā)環(huán)境和運行環(huán)境的配置,所以,本文很少涉及的以上兩方面的細節(jié)。如果,您看完《為Struts 2.0做好準備》后,還有什么不明白,或者沒法運行文中例子,請聯(lián)系我。我的E-MAIL:Max.M.Yuan@gmail.com。
在介紹常用標志前,我想先從總體上,對Struts 1.x與Struts 2.0的標...
閱讀全文
posted @
2006-10-19 15:12 xzc 閱讀(345) |
評論 (0) |
編輯 收藏
注釋簡化了數(shù)據(jù)驗證
|
|
級別: 中級
Ted Bergeron
(ted@triview.com), 合作創(chuàng)始人, Triview, Inc.
2006 年 10 月 10 日
盡管在 Web 應(yīng)用程序中盡可能多的層次中構(gòu)建數(shù)據(jù)驗證非常重要,但是這樣做卻非常耗時,以至于很多開發(fā)人員都會干脆忽略這個步驟 —— 這可能會導(dǎo)致今后大量問題的產(chǎn)生。但是隨著最新版本的 Java 平臺中引入了注釋,驗證變得簡單得多了。在本文中,Ted Bergeron 將向您介紹如何使用 Hibernate Annotations 的 Validator 組件在 Web 應(yīng)用程序中輕松構(gòu)建并維護驗證邏輯。
有時會有一種工具,它可以真正滿足開發(fā)人員和架構(gòu)師的需求。開發(fā)人員在第一次下載這種工具當天就可以在自己的應(yīng)用程序中開始使用這種工具。理論上來說,這種工具在開發(fā)人員花費大量時間來掌握其用法之前就可以從中獲益。架構(gòu)師也很喜歡這種工具,因為它可以將開發(fā)人員導(dǎo)向更高理論層次的實現(xiàn)。Hibernate Annotations 的 Validator 組件就是一種這樣的工具。
|
開始之前需要了解的內(nèi)容
在閱讀本文之前,應(yīng)該對 Java 平臺版本 5(尤其是注釋)、JSP 2.0(因為本文中創(chuàng)建了一些標簽文件,并在 TLD 中定義了一些函數(shù),它們都是 JSP 2.0 的新特性)和 Hibernate 及 Spring 框架有一個基本的了解。請注意即使不使用 Hibernate 來實現(xiàn)持久性,也可以在自己的應(yīng)用程序中使用 Hibernate Validator。
|
|
Java SE 5 為 Java 語言提供了很多需要的增強功能,不過其他增強功能可能都不如 注釋 這樣潛力巨大。使用 注釋,我們就終于具有了一個標準、一級的元數(shù)據(jù)框架為 Java 類使用。Hibernate 用戶手工編寫 *.hbm.xml 文件已經(jīng)很多年了(或者使用 XDoclet 來自動實現(xiàn)這個任務(wù))。如果手工創(chuàng)建了 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 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;
}
|
如果使用 HibernateDoclet,那么直到生成 XML 文件或運行時才能捕獲錯誤。使用 注釋,在編譯時就可以檢測出很多錯誤;或者如果在編輯時使用了很好的 IDE,那么在編輯時就可以檢測出部分錯誤。在從頭創(chuàng)建應(yīng)用程序時,可以利用 hbm2ddl 工具為自己的數(shù)據(jù)庫從 hbm.xml 文件中生成 DDL。一些重要的信息 —— 比如name
屬性的最大長度必須是 60 個字符,或者 DDL 應(yīng)該添加非空約束 —— 都被從 HibernateDoclet 項添加到 DDL 中。當使用注釋時,我們可以以類似的方式自動生成 DDL。
盡管這兩種代碼映射方式都可以使用,不過注釋的優(yōu)勢更為明顯。使用注釋,可以用一些常量來指定長度或其他值。編譯循環(huán)的速度更快,并且不需要生成 XML 文件。其中最大的優(yōu)勢是可以訪問一些有用信息,例如運行時的非空注釋或長度。除了清單 2 給出的注釋之外,還可以指定一些驗證的約束。所包含的部分約束如下:
-
@Max(value = 100)
-
@Min(value = 0)
-
@Past
-
@Future
-
@Email
在適當條件下,這些注釋會引起由 DDL 生成檢查約束。(顯然,@Future
并不是一個適當?shù)臈l件。)還可以根據(jù)需要創(chuàng)建定制約束注釋。
驗證和應(yīng)用程序?qū)?/span>
編寫驗證代碼是一個煩人且耗時的過程。通常,很多開發(fā)人員都會放棄在特定的層進行有效性驗證,從而可以節(jié)省一些時間;但是所節(jié)省的時間是否能夠彌補在這個地方因忽略部分功能所引起的缺陷卻非常值得探討。如果在所有應(yīng)用程序?qū)又袆?chuàng)建并維護驗證所需要的時間可以極大地減少,那么爭論的焦點就會轉(zhuǎn)向是否要在多個層次中進行有效性驗證。假設(shè)有一個應(yīng)用程序,它讓用戶使用一個用戶名、密碼和信用卡號來創(chuàng)建一個帳號。在這個應(yīng)用程序中所希望進行驗證的組件如下:
-
視圖: 通過 JavaScript 進行驗證可以避免與服務(wù)器反復(fù)進行交互,這樣可以提供更好的用戶體驗。用戶可以禁用 JavaScript,因此這個層次的驗證最好要有,但是卻并不可靠。對所需要的域進行簡單的驗證是必須的。
-
控制器: 驗證必須在服務(wù)器端的邏輯中進行處理。這個層次中的代碼可以以適合某個特定用途的方式處理驗證。例如,在添加新用戶時,控制器可以在進行處理之前檢查指定的用戶名是否已經(jīng)存在。
-
服務(wù): 相對復(fù)雜的業(yè)務(wù)邏輯驗證通常都最適合放到服務(wù)層中。例如,一旦有一個信用卡對象看起來有效,就應(yīng)該使用信用卡處理服務(wù)對這個信用卡的信息進行確認。
-
DAO: 在數(shù)據(jù)到達這個層次時,應(yīng)該已經(jīng)是有效的了。盡管如此,執(zhí)行一次快速檢查從而確保所需要的域都非空并且值也都在特定的范圍或遵循特定的格式(例如 e-mail 地址域就應(yīng)該包含一個有效的 e-mail 地址)也是非常有益的。在此處捕獲錯誤總比產(chǎn)生可以避免的
SQLException
錯誤要好。
-
DBMS: 這是通常可以忽略驗證的地方。即使當前正在構(gòu)建的應(yīng)用程序是數(shù)據(jù)庫的惟一客戶機,將來還可能會添加其他客戶機。如果應(yīng)用程序有一些 bug(大部分應(yīng)用程序都可能會有 bug),那么無效的數(shù)據(jù)也可能會被發(fā)送給數(shù)據(jù)庫。在這種情況中,如果走運,就可以找到無效的數(shù)據(jù),并且需要分析這些數(shù)據(jù)是否可以清除,以及如何清除。
-
模型: 這是進行驗證的一個理想地方,它不需要訪問外部服務(wù),也不需要了解持久性數(shù)據(jù)。例如,某業(yè)務(wù)邏輯可能會要求用戶至少提供一個聯(lián)系信息,這可以是一個電話號碼也可以是一個 e-mail 地址;可以使用模型層的驗證來確保用戶的確提供了這種信息。
進行驗證的一種典型方法是對簡單的驗證使用 Commons Validator,并在控制器中編寫其他一些驗證邏輯。Commons Validator 可以生成 JavaScript 來對視圖中的驗證進行處理。但是 Commons Validator 也有自己的缺陷:它只能處理簡單的驗證問題,并且將驗證的信息都保存到了 XML 文件中。Commons Validator 被設(shè)計用來與 Struts 一起使用,而且沒有提供一種簡單的方法在應(yīng)用程序?qū)娱g重用驗證的聲明。
在規(guī)劃有效性驗證策略時,選擇在錯誤發(fā)生時簡單地處理這些錯誤是遠遠不夠的。一種良好的設(shè)計同時還要通過生成一個友好的用戶界面來防止出現(xiàn)錯誤。采用預(yù)先進行的方法進行驗證可以極大地增強用戶對于應(yīng)用程序的理解。不幸的是,Commons Validator 并沒有對此提供支持。假設(shè)希望 HTML 文件設(shè)置文本域的 maxlength
屬性來與驗證匹配,或者在文本域之后放上一個百分號(%)來表示要輸入百分比的值。通常,這些信息都被硬編寫到 HTML 文檔中了。如果決定修改 name
屬性來支持 75 個字符,而不是 60 個字符,那么需要改動多少地方呢?在很多應(yīng)用程序中,通常都需要:
- 更新 DDL 來增大數(shù)據(jù)庫列的長度(通過 HibernateDoclet、 hbm.xml 或 Hibernate Annotations)。
- 更新 Commons Validator XML 文件將最大值增加到 75。
- 更新所有與這個域有關(guān)的 HTML 表單,以修改
maxlength
屬性。
更好的方法是使用 Hibernate Validator。驗證的定義都被通過注釋 添加到了模型層中,同時還有對所包含的驗證處理的支持。如果選擇充分利用所有的 Hibernate,這個 Validator 就可以在 DAO 和 DBMS 層也提供驗證。在下面給出的樣例代碼中,將使用 reflection 和 JSP 2.0 標簽文件多執(zhí)行一個步驟,從而充分利用注釋 為視圖層動態(tài)生成代碼。這可以清除在視圖中使用的硬編寫的業(yè)務(wù)邏輯。
在清單 3 中,dateOfBirth
被注釋為 NotNull
和過去的日期。 Hibernate 的 DDL 生成代碼對這個列添加了一個非空約束,以及一個要求日期必須是之前日期的檢查約束。e-mail 地址也是非空的,必須匹配 e-mail 地址的格式。這會生成一個非空約束,但是不會生成匹配這種格式的檢查約束。
清單 3. 通過 Hibernate Annotations 進行映射的簡單聯(lián)系方式
/**
* 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;
}
|
|
樣例應(yīng)用程序
在 下載 一節(jié),您可以下載一個樣例應(yīng)用程序,它展示了本文中采用的設(shè)計思想和代碼。由于這是一個可以工作的應(yīng)用程序,因此代碼比本文中討論的的內(nèi)容更為復(fù)雜。例如,清單 9 就節(jié)選于標簽文件 text.tag;這個樣例應(yīng)用程序具有標簽文件使用的所有代碼,以及其他三個類似的標簽文件使用的代碼(用于選擇、隱藏和檢查框的 HTML 元素)。由于這是一個可以工作的應(yīng)用程序,它包含了一個在這種類型的應(yīng)用程序中都可以找到的架構(gòu)。還有一個 Ant 構(gòu)建文件、Spring 和 Hibernate XML 封裝代碼,以及 log4j 配置。雖然這些都不是本文介紹的重點,但是您會發(fā)現(xiàn)仔細研究一下這個樣例應(yīng)用程序的源代碼是非常有用的。 |
|
如果需要,Hibernate DAO 實現(xiàn)也可以使用 Validation Annotations。所需做的是在 hibernate.cfg.xml 文件中指定基于 Hibernate 事件的驗證規(guī)則。(更多信息請參考 Hibernate Validator 的文檔;可以在 參考資料 一節(jié)中找到相關(guān)的鏈接)。如果真地希望抄近路,您可以只捕獲服務(wù)或控制器中的 InvalidStateException
異常,并循環(huán)遍歷 InvalidValue
數(shù)組。
對控制器添加驗證
要執(zhí)行驗證,需要創(chuàng)建一個 Hibernate 的 ClassValidator
實例。這個類進行實例化的代價可能會很高,因此最好只對希望進行驗證的每個類來進行實例化。一種方法是創(chuàng)建一個實用工具類,對每個模型對象存儲一個 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 中,創(chuàng)建了兩個 ClassValidator
,一個用于 Customer
,另外一個用于 CreditCard
。這兩個希望進行驗證的類可以調(diào)用 getInvalidValues(BaseObject modelObject)
,會返回 InvalidValue[]
。這則會返回一個包含模型對象實例錯誤的數(shù)組。另外,這個方法也可以通過提供一個特定的屬性名來調(diào)用,這樣做會只返回與該域有關(guān)的錯誤。
在使用 Spring MVC 和 Hibernate Validator 時,為信用卡創(chuàng)建一個驗證過程變得非常簡單,如清單 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
數(shù)組。如果發(fā)現(xiàn)了一個或多個這種簡單錯誤,那么就可以將 Hibernate 的 InvalidValue
數(shù)組轉(zhuǎn)換成 Spring 的 Errors
對象。如果用戶已經(jīng)創(chuàng)建了這個信用卡并且沒有出現(xiàn)任何簡單錯誤,就可以將更加徹底的驗證委托給服務(wù)層進行。這一層可以與商業(yè)服務(wù)提供者一起對信用卡進行驗證。
現(xiàn)在我們已經(jīng)看到這個簡單的模型層注釋是如何平衡到控制器、DAO 和 DBMS 層的驗證的。在 HibernateDoclet 和 Commons Validator 中發(fā)現(xiàn)的驗證邏輯的重合現(xiàn)在都已經(jīng)統(tǒng)一到模型中了。盡管這是一個非常受歡迎的改進,但是視圖層傳統(tǒng)上來說一直是最需要進行詳細驗證的地方。
為視圖添加驗證
在下面的例子中,使用了 Spring MVC 和 JSP 2.0 標簽文件。JSP 2.0 允許在 TLD 文件中對定制函數(shù)進行注冊,并在一個標簽文件中進行調(diào)用。標簽文件類似于 taglibs,但是它們是使用 JSP 代碼編寫的,而不是使用 Java 代碼編寫的。采用這種方法,使用 Java 語言寫好的代碼就可以封裝成函數(shù),而使用 JSP 寫好的代碼則可以放入標簽文件中。在這種情況中,對注釋的處理需要使用映像,這會由幾個函數(shù)來執(zhí)行。綁定 Spring 或呈現(xiàn) XHTML 的代碼也是標簽文件的一部分。
清單 6 中節(jié)選的 TLD 代碼定義 text.tag 文件可以使用,并定義了一個名為 required
的函數(shù)。
清單 6. 創(chuàng)建表單 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 節(jié)選自 Utilities
類,其中包含了標簽文件使用的所有函數(shù)。在前文中我們曾經(jīng)說過,最適合使用 Java 代碼編寫的代碼都被放到了幾個 TLD 可以映射的函數(shù)中,這樣標簽文件就可以使用它們了;這些函數(shù)都是在 Utilities
類中進行編碼的。因此,我們需要三樣?xùn)|西:定義這些類的 TLD 文件、Utilities
中的函數(shù),以及標簽文件本身,后者要使用這些函數(shù)。(第四樣應(yīng)該是使用這個標簽文件的 JSP 頁面。)
在清單 7 中,給出了在 TLD 中引用的函數(shù)和另外一個表示給定屬性是否是 Date
的方法。在這個類中要涉及到比較多的代碼,但是本文限于篇幅,不會給出所有的代碼;不過需要注意 findGetterMethod()
除了將表達式語言(Expression Language,EL)方法表示(customer.contact
)轉(zhuǎn)換成 Java 表示(customer.getContact()
)之外,還執(zhí)行了基本的映像操作。
清單 7. Utilities 節(jié)選
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 方法,并檢查這個方法是否有相關(guān)的給定的注釋 。
清單 8 中給出的 JSP 例子進行了簡化,這樣就可以著重查看相關(guān)的部分了。此處,這里有一個表單,它有一個選擇框和兩個輸入域。所有這些域都是通過在 form.tld 文件中聲明的標簽文件進行呈現(xiàn)的。標簽文件被設(shè)計成使用智能缺省值,這樣就可以根據(jù)需要允許簡單編碼的 JSP 可以有定義更多信息的選項。關(guān)鍵的屬性是 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 給出了其中關(guān)鍵的部分:
清單 9. 標簽文件 text.tag 節(jié)選
<%@ 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
被設(shè)置為在 propertyPath
中引用的屬性的父對象。因此,如果 propertyPath
是 customer.contact.fax.number
, 那么 objectPath
就應(yīng)該被設(shè)置為 customer.contact.fax
。我們現(xiàn)在就使用 Spring 的 bind
標簽綁定到了包含屬性的對象上。這會將對象變量設(shè)置成對包含屬性的實例的引用。接下來,檢查這個標簽的用戶是否已經(jīng)指定他/她們是否希望屬性是必須的。允許表單開發(fā)人員覆蓋從注釋中返回的值是非常重要的,因為有時他/她們希望讓控制器為所需要的域設(shè)置缺省值,而用戶可能并不希望為這個域提供值。如果表單開發(fā)人員沒有為 required
指定值,那么就可以調(diào)用這個表單 TLD 的 required
函數(shù)。這個函數(shù)調(diào)用了在 TLD 文件中映射的方法。這個方法簡單地檢查 @NotNull
注釋;如果它發(fā)現(xiàn)某個屬性具有這個注釋,就將 labelClass
變量設(shè)置為必須的。可以類似地確定正確的 maxlength
以及這個域是否是一個 Date
。
接下來使用 Spring 來綁定到 propertyPath
上,而不是像前面一樣只綁定到包含這個屬性的對象上。這允許在生成 label
和 input
HTML 標簽時使用 status.expression
和 status.value
。 input
標簽也可以使用一個大小 maxlength
以及適當?shù)念悂砩伞H绻懊嬉呀?jīng)確定屬性是一個 Date
,現(xiàn)在就可以添加 JavaScript 日歷了。(可以在 參考資料 一節(jié)找到一個很好的日歷組件的鏈接)。注意根據(jù)需要鏈接屬性、輸入 ID 和圖像 ID 的標簽是多么簡單。)這個 JavaScript 日歷需要一個圖像 ID 來匹配輸入域,其后綴是 _button
。
最后,可以將 <jsp:doBody/>
封裝到一個 span
標簽中,這樣允許表單開發(fā)人員在頁面中添加其他圖標,例如用來尋求幫助的圖標。(清單 8 給出了一個為信用卡號域添加的幫助圖標。)最后的部分是檢查 Spring 是否為這個屬性報告和顯示了一個錯誤,并和一個錯誤圖標一起顯示。
使用 CSS,就可以對必須的域進行一下裝飾 —— 例如,讓它們以紅色顯示、在文本邊上顯示一個星號,或者使用一個背景圖像來裝飾它。在清單 10 中,將必須的域的標簽設(shè)置成黑色,而且后面顯示一個紅色的星號(在 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 日歷圖標。對所有的文本域設(shè)置正確的 maxlength
屬性可以防止用戶輸入太多文本所引起的錯誤。可以擴展 text
標簽來為輸入域類設(shè)置其他的數(shù)據(jù)類型。可以修改 text
標簽使用 HTML,而不是 XHTML(如果希望這樣)。可以不太費力地獲得具有正確語義的 HTML 表單,而且不需學(xué)習基于組件的框架知識,就可以利用基于組件的 Web 框架的優(yōu)點。
盡管標簽文件生成的 HTML 文件可以幫助防止一些錯誤的產(chǎn)生,但是在視圖層并沒有任何代碼來真正進行錯誤檢查。由于可以使用類屬性,現(xiàn)在就可以添加一些簡單的 JavaScript 來實現(xiàn)這種功能了,如清單 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 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>
|
這個 JavaScript 是通過為表單聲明添加 onsubmit="return checkRequired(this);"
被調(diào)用的。這個腳本簡單地獲取具有所需要的類的表單中的所有元素。由于我們的習慣是在標簽標記中使用這個類,因此代碼會通過 for
屬性來查找與這個標簽連接在一起的輸入域。如果任何輸入域為空,就會生成一條簡單的警告消息,表單提交就會取消。可以簡單地對這個腳本進行擴充,使其掃描多個類,并相應(yīng)地進行驗證。
對于基于 JavaScript 的綜合的驗證集合來說,最好是使用開源實現(xiàn),而不是自行開發(fā)。在清單 8 中您可能已經(jīng)注意到下面的代碼:
onclick="new Effect.SlideDown('creditCardHelp')"
|
這個函數(shù)是 Script.aculo.us 庫的一部分,這個庫提供了很多高級的效果。如果正在使用 Script.aculo.us,就需要對所構(gòu)建的內(nèi)容使用 Prototype 庫。 JavaScript 驗證庫的一個例子是由 Andrew Tetlaw 在 Prototype 基礎(chǔ)上構(gòu)建的。(請參看 參考資料 一節(jié)中的鏈接。)他的框架依賴于被添加到輸入域的類:
<input class="required validate-number" id="field1" name="field1" />
|
可以簡單地修改 text.tag 的邏輯在 input
標簽中插入幾個類。將 class="required"
添加到輸入標簽和 label
標簽中不會影響 CSS 規(guī)則,但會破壞清單 10 中給出的簡單 JavaScript 驗證程序。如果要混合使用框架中的代碼和簡單的 JavaScript 代碼,最好使用不同的類名,或在使用類名搜索元素時確保類名有效并檢查標簽類型。
最后的考慮
本文已經(jīng)介紹了模型層的注釋如何充分用來在視圖、控制器、DAO 和 DBMS 層中創(chuàng)建驗證。必須手工創(chuàng)建服務(wù)層的驗證,例如信用卡驗證。其他模型層的驗證,例如強制屬性 C 是必須的,而屬性 A 和 B 都處于指定的狀態(tài),這也是一個手工任務(wù)。然而,使用 Hibernate Annotations 的 Validator 組件,就可以輕松地聲明并應(yīng)用大多數(shù)驗證。
展望
不論是簡單例子還是所引用框架的 JavaScript 驗證都可以對簡單的條件進行檢查,例如域必須要填寫,或者客戶機端代碼中的數(shù)據(jù)類型必須要匹配預(yù)期的類型。需要用到服務(wù)器端邏輯的驗證可以使用 Ajax 添加到 JavaScript 驗證程序中。您可以使用一個用戶注冊界面來讓用戶可以選擇用戶名。文本標簽可以進行增強來檢查 @Column(unique = true)
注釋。在找到這個注釋時,標簽可以添加一個用來觸發(fā) Ajax 調(diào)用的類。
現(xiàn)在您不需要在應(yīng)用程序?qū)娱g維護重復(fù)的驗證邏輯了,這樣就可以節(jié)省出大量的開發(fā)時間。想像一下您最終可以為應(yīng)用程序所能添加的增強功能!
下載
描述 |
名字 |
大小 |
下載方法 |
示例應(yīng)用程序 |
j-hibval-source.zip |
8MB |
HTTP
|
參考資料
學(xué)習
獲得產(chǎn)品和技術(shù)
討論
關(guān)于作者
|
|
|
Ted Bergeron 是 Triview 的合作創(chuàng)始人之一,Triview 是一家企業(yè)軟件咨詢公司,位于加利福尼亞的圣地亞哥。Ted 從事基于 Web 的應(yīng)用程序的設(shè)計已經(jīng)有十 多年的時間了。他所做過的一些知名的項目包括為 Sybase、Orbitz、Disney 和 Qualcomm 所設(shè)計的項目。Ted 還曾用三 年的時間作為一名技術(shù)講師來教授有關(guān) Web 開發(fā)、Java 開發(fā)和數(shù)據(jù)庫邏輯設(shè)計的課程。您可以在 Triview 的 Web 站點 上了解有關(guān) Triview 公司的更多內(nèi)容,或者也可以撥打該公司的免費電話 (866)TRIVIEW。
|
posted @
2006-10-16 14:19 xzc 閱讀(735) |
評論 (0) |
編輯 收藏
摘要: 大多數(shù)開發(fā)人員現(xiàn)在還在使用if else的過程結(jié)構(gòu),曾看過jdon的banq大哥寫的一篇文章,利用command,aop模式替代if else過程結(jié)構(gòu)。當時還不太明白,這幾天看了《重構(gòu)》第一章的影片租賃案例,感觸頗深。下面我來談一談為什么要用state pattern替代if else,替代if else有什么好處,以及給出詳細代碼怎么替代if else。本文參考jdon的“你還在使用if els...
閱讀全文
posted @
2006-10-16 13:46 xzc 閱讀(3152) |
評論 (0) |
編輯 收藏
FCKeditor,作為現(xiàn)在功能最強大的在線HTML編輯器,網(wǎng)上關(guān)于他的功能介紹以及基本配置已經(jīng)很多了。然而其中不少文章涉及面都比較局限。最近,筆者需要在自己項目中使用到FCKeditor,并用之于和已有的基于JSF的web應(yīng)用整合。從對FCKeditor一竅不通到成功達成整合,我從網(wǎng)上學(xué)到了不少知識,自己也積累了不少經(jīng)驗,因此,也想和大家一起分析這一過程。
??
1.基本配置:
????? 知之為知之,不知google之。關(guān)于FCKeditor的基本配置在網(wǎng)上自有高人指點,這里我也不多耽誤大家時間。主要是談下自己當初配置的問題:
??? a.基本路徑:
??????? 注意FCKeditor的基本路徑應(yīng)該是/(你的web應(yīng)用名稱)/(放置FCKeditor文件的文件夾名)/
??????? 我的目錄結(jié)構(gòu)為:
???????

??????? 因此,我的基本路徑設(shè)置為:/fck/FCKeditor/
???? b.文件瀏覽以及上傳路徑設(shè)置:
??????? 我個人的參考如下:
??????
FCKConfig.LinkBrowser?=?true?;
FCKConfig.LinkBrowserURL?=?FCKConfig.BasePath?+?'filemanager/browser/default/browser.html?Connector=connectors/jsp/connector'?;
FCKConfig.LinkBrowserWindowWidth????=?FCKConfig.ScreenWidth?*?0.7?;????????//?70%
FCKConfig.LinkBrowserWindowHeight????=?FCKConfig.ScreenHeight?*?0.7?;????//?70%

FCKConfig.ImageBrowser?=?true?;
FCKConfig.ImageBrowserURL?=?FCKConfig.BasePath?+?'filemanager/browser/default/browser.html?Type=Image&Connector=connectors/jsp/connector';
FCKConfig.ImageBrowserWindowWidth??=?FCKConfig.ScreenWidth?*?0.7?;????//?70%?;
FCKConfig.ImageBrowserWindowHeight?=?FCKConfig.ScreenHeight?*?0.7?;????//?70%?;

FCKConfig.FlashBrowser?=?true?;
FCKConfig.FlashBrowserURL?=?FCKConfig.BasePath?+?'filemanager/browser/default/browser.html?Type=Flash&Connector=connectors/jsp/connector'?;
FCKConfig.FlashBrowserWindowWidth??=?FCKConfig.ScreenWidth?*?0.7?;????//70%?;
FCKConfig.FlashBrowserWindowHeight?=?FCKConfig.ScreenHeight?*?0.7?;????//70%?;

FCKConfig.LinkUpload?=?true?;
FCKConfig.LinkUploadURL?=?FCKConfig.BasePath?+?'filemanager/upload/simpleuploader?Type=File'?;
FCKConfig.LinkUploadAllowedExtensions????=?""?;????????????//?empty?for?all
FCKConfig.LinkUploadDeniedExtensions????=?".(php|php3|php5|phtml|asp|aspx|ascx|jsp|cfm|cfc|pl|bat|exe|dll|reg|cgi)$"?;????//?empty?for?no?one

FCKConfig.ImageUpload?=?true?;
FCKConfig.ImageUploadURL?=FCKConfig.BasePath?+?'filemanager/upload/simpleuploader?Type=Image'?;
FCKConfig.ImageUploadAllowedExtensions????=?".(jpg|gif|jpeg|png)$"?;????????//?empty?for?all
FCKConfig.ImageUploadDeniedExtensions????=?""?;????????????????????????????//?empty?for?no?one

FCKConfig.FlashUpload?=?true?;
FCKConfig.FlashUploadURL?=?FCKConfig.BasePath?+?'filemanager/upload/simpleuploader?Type=Flash'?;
FCKConfig.FlashUploadAllowedExtensions????=?".(swf|fla)$"?;????????//?empty?for?all
FCKConfig.FlashUploadDeniedExtensions????=?""?;????????????????????//?empty?for?no?one 2.與JSF整合。
? FCKeditor與JSF整合的最大問題,在于需要從JSF環(huán)境中相應(yīng)Bean讀取值賦予給FCKeditor,同時從FCKeditor里面讀取結(jié)果賦予給相應(yīng)的數(shù)據(jù)持有Bean。由于這一過程在傳統(tǒng)的JSF標簽中是通過值綁定有框架自動完成,因此這里需要我們手動來實現(xiàn)這一過程。
? 以下要展示的demo由兩部分組成:
?? form.jsp顯示編輯內(nèi)容,點擊其submit按鈕跳轉(zhuǎn)至test.jsp,test.jsp調(diào)用FCKeditor對form.jsp中顯示的已有內(nèi)容進行顯示和編輯。編輯完后點擊submit,頁面將重新跳轉(zhuǎn)到form.jsp,顯示修改后的內(nèi)容。
? 其實,這就是一個很基本的編輯功能,在論壇和blog中都可以看到它的身影。
?? 而ContextBean用于持有待編輯的內(nèi)容,它的scope是session范圍。ContextServlet用于讀取FCKeditor修改后的內(nèi)容,并賦予ContextBean中。
??? 首先來看form.jsp
<body>??
????????<f:view>
????????????<h:form>
????????????????<pre>
????????????????<h:outputText?value="#{td.name}"?escape="false"></h:outputText>
????????????????</pre>
????????????????????????????????<br/>
????????????????<h:commandButton?value="submit"?action="#{td.submit}"></h:commandButton>
????????????</h:form>
????????</f:view>
????</body>??? 這個頁面很簡單,其中td對應(yīng)的就是ContextBean,ContextBean.name用于保存編輯內(nèi)容
package?com.dyerac;


public?class?ContextBean?
{
????private?String?name;


????public?String?getName()?
{
????????return?name;
????}


????public?void?setName(String?name)?
{
????????this.name?=?name;
????}


????public?String?submit()?
{
????????
????????return?"edit";
????}
}下面來看test.jsp
?用過FCKeditor的都知道,F(xiàn)CKeditor可以通過三種方式來調(diào)用:
?script, jsp 標簽以及java代碼。
這里,為了方便從ContextBean中讀取name屬性內(nèi)容作為其初始值,我使用了第三種方法
其中
FCKeditor?fck=new?FCKeditor(request,"EditorDefault");初始化FCKeditor,第二個參數(shù)即為該FCKeditor實例的id,當提交后FCKeditor內(nèi)的內(nèi)容將以該參數(shù)為變量名保存在request中。
比如,這里我選擇了
EditorDefault,所以日后讀取FCKeditor內(nèi)容時,可以通過以下語句:
request.getParameter("EditorDefault")
<body>
????????<form?action="/fck/servlet/ContextServlet"?method="post"?target="_blank">
????????<%FCKeditor?fck=new?FCKeditor(request,"EditorDefault");
??????????FacesContext?fcg=FacesContext.getCurrentInstance();
??????????ContextBean?td=(ContextBean)fcg.getApplication().getVariableResolver().resolveVariable(fcg,"td");
??????????fck.setBasePath("/fck/FCKeditor/");
??????????fck.setValue(td.getName());
??????????fck.setHeight(new?Integer(600).toString());
??????????out.print(fck.create());
?????????%>
?????????<input?type="submit"?value="Submit">
????</body>?修改后的結(jié)果以post方式提交給/fck/servlet/ContextServlet,該url對應(yīng)的即為ContextServlet。
ContextServlet負責讀取FCKeditor里的內(nèi)容,并賦予給session中的ContextBean。doPost()方法用于實現(xiàn)該功能
public?void?doPost(HttpServletRequest?request,?HttpServletResponse?response)

????????????throws?ServletException,?IOException?
{
????????FacesContext?fcg?=?getFacesContext(request,response);
????????ContextBean?td?=?(ContextBean)?fcg.getApplication()
????????????????.getVariableResolver().resolveVariable(fcg,?"td");
????????String?name=new?String(request.getParameter("EditorDefault").getBytes("ISO-8859-1"),"UTF-8");
????????td.setName(name);
????????RequestDispatcher?rd=getServletContext().getRequestDispatcher("/form.faces");
????????rd.forward(request,?response);
????}需要注意兩個問題,
其一:FCKeditor內(nèi)的中文信息讀取是可能出現(xiàn)亂碼,需要額外的處理:
?? String?name=new?String(request.getParameter("EditorDefault").getBytes("ISO-8859-1"),"UTF-8");
其二:由于servlet處于FacesContext范圍外,因此不能通過FacesContext.getCurrentInstance()來得到當前FacesContext,因此ContextServlet定義了自己的方法用于獲取FacesContext:
//?????You?need?an?inner?class?to?be?able?to?call?FacesContext.setCurrentInstance
//?????since?it's?a?protected?method
????private?abstract?static?class?InnerFacesContext?extends?FacesContext

????
{

??????protected?static?void?setFacesContextAsCurrentInstance(FacesContext?facesContext)?
{
????????FacesContext.setCurrentInstance(facesContext);
??????}
????}


????private?FacesContext?getFacesContext(ServletRequest?request,?ServletResponse?response)?
{
??????//?Try?to?get?it?first
??????FacesContext?facesContext?=?FacesContext.getCurrentInstance();
??????if?(facesContext?!=?null)?return?facesContext;

??????FacesContextFactory?contextFactory?=?(FacesContextFactory)FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
??????LifecycleFactory?lifecycleFactory?=?(LifecycleFactory)FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
??????Lifecycle?lifecycle?=?lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);

??????//?Either?set?a?private?member?servletContext?=?filterConfig.getServletContext();
??????//?in?you?filter?init()?method?or?set?it?here?like?this:
??????//?ServletContext?servletContext?=?((HttpServletRequest)request).getSession().getServletContext();
??????//?Note?that?the?above?line?would?fail?if?you?are?using?any?other?protocol?than?http
??????ServletContext?servletContext?=?((HttpServletRequest)request).getSession().getServletContext();

??????//?Doesn't?set?this?instance?as?the?current?instance?of?FacesContext.getCurrentInstance
??????facesContext?=?contextFactory.getFacesContext(servletContext,?request,?response,?lifecycle);

??????//?Set?using?our?inner?class
??????InnerFacesContext.setFacesContextAsCurrentInstance(facesContext);

??????//?set?a?new?viewRoot,?otherwise?context.getViewRoot?returns?null
??????//UIViewRoot?view?=?facesContext.getApplication().getViewHandler().createView(facesContext,?"yourOwnID");
??????//facesContext.setViewRoot(view);

??????return?facesContext;
????}??? ContextServlet處理完了FCKeditor內(nèi)容后,將跳轉(zhuǎn)到form.jsp。
這樣一個簡單的編輯功能就完成了。
3.遺留問題:
?? 我在上傳文件時還是會出現(xiàn)中文亂碼的問題,按照其他人說的那樣把網(wǎng)頁的charset=utf-8改成gb2312一樣會有問題,還請各位高手賜教^_^
另外,關(guān)于整個demo的源代碼如果大家需要,可以留言給我,我用郵箱發(fā)給您。不足之處,還請各位多多指點
posted @
2006-10-14 17:13 xzc 閱讀(426) |
評論 (0) |
編輯 收藏
關(guān)鍵字: ? ????
開發(fā)環(huán)境: Tomcat5.5 Eclipse3.2
FCKeditor 版本 FCKeditor_2.3.1 FCKeditor.Java 2.3
下載地址: http://www.fckeditor.net/download/default.html
開始: 新建工程,名稱為 fekeditor 解壓 FCKeditor_2.3.1 包中的 edit 文件夾到項目中的 WebContent 目錄
解壓 FCKeditor_2.3.1 包中的 fckconfig.js、fckeditor.js、fckstyles.xml、fcktemplates.xml 文件夾到項目中的 WebContent 目錄
解壓 FCKeditor-2.3.zip 包中的 \web\WEB-INF\lib 下的兩個 jar 文件到項目的 WebContent\WEB-INF\lib 目錄
解壓 FCKeditor-2.3.zip 包中的 \src 下的 FCKeditor.tld 文件到項目的 WebContent\WEB-INF 目錄
刪除 WebContent\edit 目錄下的 _source 文件夾
修改 web.xml 文件,加入以下內(nèi)容
代碼
<
servlet
>
<
servlet
-
name
>
Connector
</
servlet-name>
<servlet-class>
com.fredck.FCKeditor.connector.ConnectorServlet
<
/
servlet
-
class
>
<
init
-
param
>
<
param
-
name
>
baseDir
</
param-name>
<!-- 此為文件瀏覽路徑 -->
<param-value>
/
UserFiles
/</
param-value>
<
/
init
-
param
>
<
init
-
param
>
<
param
-
name
>
debug
</
param-name>
<param-value>true<
/
param
-
value
>
</
init-param>
<load-on-startup>1<
/
load
-
on
-
startup
>
</
servlet> <servlet>
<servlet-name>SimpleUploader<
/
servlet
-
name
>
<
servlet
-
class
>
com
.
fredck
.
FCKeditor
.
uploader
.
SimpleUploaderServlet
</
servlet-class>
<init-param>
<param-name>baseDir<
/
param
-
name
>
<!-- 此為文件上傳路徑,需要在WebContent 目錄下新建 UserFiles 文件夾 --> <!-- 根據(jù)文件的類型還需要新建相關(guān)的文件夾 Image、Flash--> <param-value>/UserFiles/</param-value>
</init-param> <init-param> <param-name>debug</param-name>
<param-value>true</param-value> </init-param>
<init-param>
<!-- 此參數(shù)為是否開啟上傳功能 -->
<param-name>enabled</param-name> <param-value>false</param-value>
</init-param> <init-param> <param-name>AllowedExtensionsFile</param-name>
<param-value></param-value> </init-param>
<init-param> <!-- 此參數(shù)為文件過濾,以下的文件類型都不可以上傳 -->
<param-name>DeniedExtensionsFile</param-name> <param-value> php|php3|php5|phtml|asp|aspx|ascx|jsp|cfm|cfc|pl|bat|exe|dll|reg|cgi </param-value>
</init-param> <init-param> <param-name>AllowedExtensionsImage</param-name>
<param-value>jpg|gif|jpeg|png|bmp</param-value> </init-param>
<init-param>
<param-name>DeniedExtensionsImage</param-name> <param-value></param-value>
</init-param> <init-param> <param-name>AllowedExtensionsFlash</param-name>
<param-value>swf|fla</param-value> </init-param>
<init-param>
<param-name>DeniedExtensionsFlash</param-name> <param-value></param-value>
</init-param> <load-on-startup>1</load-on-startup>
</servlet><servlet-mapping> <servlet-name>Connector</servlet-name>
<url-pattern>
/editor/filemanager/browser/default/connectors/jsp/connector </url-pattern>
</servlet-mapping><servlet-mapping> <servlet-name>SimpleUploader</servlet-name>
<url-pattern>
/editor/filemanager/upload/simpleuploader </url-pattern>
</servlet-mapping>
新建一個提交頁 jsp1.jsp 文件和一個接收頁 jsp2.jsp 文件
jsp1.jsp 代碼如下: 代碼 <%@ page contentType = "text/html;charset=UTF-8" language = "java" %>
<%@tagliburi="/WEB-INF/FCKeditor.tld"prefix="fck"%><html> <head> <title>Test</title>
</head><body> <FORMaction="jsp2.jsp"> <fck:editorid="testfck"basePath="/fekeditor/" height="100%" skinPath="/fekeditor/editor/skins/default/" toolbarSet="Default" imageBrowserURL="/fekeditor/editor/filemanager/browser/default/browser.html?Type=Image&Connector=connectors/jsp/connector" linkBrowserURL="/fekeditor/editor/filemanager/browser/default/browser.html?Connector=connectors/jsp/connector" flashBrowserURL="/fekeditor/editor/filemanager/browser/default/browser.html?Type=Flash&Connector=connectors/jsp/connector" imageUploadURL="/fekeditor/editor/filemanager/upload/simpleuploader?Type=Image" linkUploadURL="/fekeditor/editor/filemanager/upload/simpleuploader?Type=File" flashUploadURL="/fekeditor/editor/filemanager/upload/simpleuploader?Type=Flash"> </fck:editor>
<input type="submit" /> </FORM>
</body> </html>jsp2.jsp 代碼如下:<html>
<head>
<title>TEST</title> </head> <body>
<%=request.getParameter( "testfck" )%>
</body> </html> 在 WebContent 目錄下新建 UserFiles 文件夾,在此文件夾下新建 File,Image,Flash 三個文件夾。
ok現(xiàn)在運行一下看看吧!
|
posted @
2006-10-14 17:08 xzc 閱讀(503) |
評論 (0) |
編輯 收藏
試用了一下FCKeditor,根據(jù)網(wǎng)上的文章小結(jié)一下: 1.下載 FCKeditor.java 2.3 (FCKeditot for java) FCKeditor 2.2 (FCKeditor基本文件)
2.建立項目:tomcat/webapps/TestFCKeditor.
3.將FCKeditor2.2解壓縮,將整個目錄FCKeditor復(fù)制到項目的根目錄下, 目錄結(jié)構(gòu)為:tomcat/webapps/TestFCKeditor/FCKeditor 然后將FCKeditor-2.3.zip(java)壓縮包中\(zhòng)web\WEB-INF\lib\目錄下的兩個jar文件拷到項目的\WEB-INF\lib\目錄下。把其中的src目錄下的FCKeditor.tld文件copy到TestFCKedit/FCKeitor/WEB-INF/下
4.將FCKeditor-2.3.zip壓縮包中\(zhòng)web\WEB-INF\目錄下的web.xml文件合并到項目的\WEB-INF\目錄下的web.xml文件中。
|
5. 修改合并后的web.xml文件,將名為SimpleUploader的Servlet的enabled參數(shù)值改為true,
以允許上傳功能,Connector Servlet的baseDir參數(shù)值用于設(shè)置上傳文件存放的位置。
添加標簽定義:
<taglib>
<taglib-uri>/TestFCKeditor</taglib-uri>
<taglib-location>/WEB-INF/FCKeditor.tld</taglib-location>
</taglib>
運行圖:
6. 上面文件中兩個servlet的映射分別為:/editor/filemanager/browser/default/connectors/jsp/connector
和/editor/filemanager/upload/simpleuploader,需要在兩個映射前面加上/FCKeditor,
即改為/FCKeditor/editor/filemanager/browser/default/connectors/jsp/connector和
/FCKeditor/editor/filemanager/upload/simpleuploader。
7.進入skin文件夾,如果你想使用fckeditor默認的這種奶黃色,
那就把除了default文件夾外的另兩個文件夾直接刪除.
8.刪除/FCKeditor/目錄下除fckconfig.js, fckeditor.js, fckstyles.xml, fcktemplates.xml四個文件以外的所有文件
刪除目錄/editor/_source,
刪除/editor/filemanager/browser/default/connectors/下的所有文件
刪除/editor/filemanager/upload/下的所有文件
刪除/editor/lang/下的除了fcklanguagemanager.js, en.js, zh.js, zh-cn.js四個文件的所有文件
9.打開/FCKeditor/fckconfig.js
修改 FCKConfig.DefaultLanguage = 'zh-cn' ;
把FCKConfig.LinkBrowserURL等的值替換成以下內(nèi)容:
FCKConfig.LinkBrowserURL
= FCKConfig.BasePath + "filemanager/browser/default/browser.html?Connector=connectors/jsp/connector" ;
FCKConfig.ImageBrowserURL
= FCKConfig.BasePath + "filemanager/browser/default/browser.html?Type=Image&Connector=connectors/jsp/connector" ;
FCKConfig.FlashBrowserURL
= FCKConfig.BasePath + "filemanager/browser/default/browser.html?Type=Flash&Connector=connectors/jsp/connector" ;
FCKConfig.LinkUploadURL = FCKConfig.BasePath + 'filemanager/upload/simpleuploader?Type=File' ;
FCKConfig.FlashUploadURL = FCKConfig.BasePath + 'filemanager/upload/simpleuploader?Type=Flash' ;
FCKConfig.ImageUploadURL = FCKConfig.BasePath + 'filemanager/upload/simpleuploader?Type=Image' ;
10.其它
fckconfig.js總配置文件,可用記錄本打開,修改后將文件存為utf-8 編碼格式。找到:
FCKConfig.TabSpaces = 0 ; 改為FCKConfig.TabSpaces = 1 ; 即在編輯器域內(nèi)可以使用Tab鍵。
如果你的編輯器還用在網(wǎng)站前臺的話,比如說用于留言本或是日記回復(fù)時,那就不得不考慮安全了,
在前臺千萬不要使用Default的toolbar,要么自定義一下功能,要么就用系統(tǒng)已經(jīng)定義好的Basic,
也就是基本的toolbar,找到:
FCKConfig.ToolbarSets["Basic"] = [
['Bold','Italic','-','OrderedList','UnorderedList','-',/*'Link',*/'Unlink','-','Style','FontSize','TextColor','BGColor','-',
'Smiley','SpecialChar','Replace','Preview'] ] ;
這是改過的Basic,把圖像功能去掉,把添加鏈接功能去掉,因為圖像和鏈接和flash和圖像按鈕添加功能都能讓前臺
頁直接訪問和上傳文件, fckeditor還支持編輯域內(nèi)的鼠標右鍵功能。
FCKConfig.ContextMenu = ['Generic',/*'Link',*/'Anchor',/*'Image',*/'Flash','Select','Textarea','Checkbox','Radio','TextField','HiddenField',
/*'ImageButton',*/'Button','BulletedList','NumberedList','TableCell','Table','Form'] ;
這也是改過的把鼠標右鍵的“鏈接、圖像,F(xiàn)LASH,圖像按鈕”功能都去掉。
找到: FCKConfig.FontNames = 'Arial;Comic Sans MS;Courier New;Tahoma;Times New Roman;Verdana' ;
加上幾種我們常用的字體
FCKConfig.FontNames
= '宋體;黑體;隸書;楷體_GB2312;Arial;Comic Sans MS;Courier New;Tahoma;Times New Roman;Verdana' ;
7.添加文件 /TestFCKeditor/test.jsp:
<%@ page language="java" import="com.fredck.FCKeditor.*" %>
<%@ taglib uri="/TestFCKeditor" prefix="FCK" %>
<script type="text/javascript" src="/TestFCKeditor/FCKeditor/fckeditor.js"></script>
<%--
三種方法調(diào)用FCKeditor
1.FCKeditor自定義標簽 (必須加頭文件 <%@ taglib uri="/TestFCKeditor" prefix="FCK" %> )
2.script腳本語言調(diào)用 (必須引用 腳本文件 <script type="text/javascript" src="/TestFCKeditor/FCKeditor/fckeditor.js"></script> )
3.FCKeditor API 調(diào)用 (必須加頭文件 <%@ page language="java" import="com.fredck.FCKeditor.*" %> )
--%>
<%--
<form action="show.jsp" method="post" target="_blank">
<FCK:editor id="content" basePath="/TestFCKeditor/FCKeditor/"
width="700"
height="500"
skinPath="/TestFCKeditor/FCKeditor/editor/skins/silver/"
toolbarSet = "Default"
>
input
</FCK:editor>
<input type="submit" value="Submit">
</form>
--%>
<form action="show.jsp" method="post" target="_blank">
<table border="0" width="700"><tr><td>
<textarea id="content" name="content" style="WIDTH: 100%; HEIGHT: 400px">input</textarea>
<script type="text/javascript">
var oFCKeditor = new FCKeditor('content') ;
oFCKeditor.BasePath = "/TestFCKeditor/FCKeditor/" ;
oFCKeditor.Height = 400;
oFCKeditor.ToolbarSet = "Default" ;
oFCKeditor.ReplaceTextarea();
</script>
<input type="submit" value="Submit">
</td></tr></table>
</form>
<%--
<form action="show.jsp" method="post" target="_blank">
<%
FCKeditor oFCKeditor ;
oFCKeditor = new FCKeditor( request, "content" ) ;
oFCKeditor.setBasePath( "/TestFCKeditor/FCKeditor/" ) ;
oFCKeditor.setValue( "input" );
out.println( oFCKeditor.create() ) ;
%>
<br>
<input type="submit" value="Submit">
</form>
--%>
添加文件/TestFCKeditor/show.jsp:
<%
String content = request.getParameter("content");
out.print(content);
%>
8.瀏覽http://localhost:8080/TestFCKeditor/test.jsp
ok!
posted @
2006-10-11 17:19 xzc 閱讀(223) |
評論 (0) |
編輯 收藏
最近寫書,寫到JNDI,到處查資料,發(fā)現(xiàn)所有的中文資料都對JNDI解釋一通,配置代碼也是copy的,調(diào)了半天也沒調(diào)通,最后到SUN的網(wǎng)站參考了一下他的JNDI tutorial,終于基本上徹底明白了
和多數(shù)java服務(wù)一樣,SUN對JNDI也只提供接口,使用JNDI只需要用到JNDI接口而不必關(guān)心具體實現(xiàn):
private static Object jndiLookup() throws Exception {
InitialContext ctx = new InitialContext();
return ctx.lookup("java:comp/env/systemStartTime");
}
上述代碼在J2EE服務(wù)器環(huán)境下工作得很好,但是在main()中就會報一個NoInitialContextException,許多文章會說你創(chuàng)建InitialContext的時候還要傳一個Hashtable或者Properties,像這樣:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
env.put(Context.PROVIDER_URL,"t3://localhost:7001");
InitialContext ctx = new InitialContext(env);
這個在WebLogic環(huán)境下是對的,但是換到JBoss呢?再用JBoss的例子?
其實之所以有NoInitialContextException是因為無法從System.properties中獲得必要的JNDI參數(shù),在服務(wù)器環(huán)境下,服務(wù)器啟動時就把這些參數(shù)放到System.properties中了,于是直接new InitialContext()就搞定了,不要搞env那么麻煩,搞了env你的代碼還無法移植,弄不好管理員設(shè)置服務(wù)器用的不是標準端口還照樣拋異常。
但是在單機環(huán)境下,可沒有JNDI服務(wù)在運行,那就手動啟動一個JNDI服務(wù)。我在JDK 5的rt.jar中一共找到了4種SUN自帶的JNDI實現(xiàn):
LDAP,CORBA,RMI,DNS。
這4種JNDI要正常運行還需要底層的相應(yīng)服務(wù)。一般我們沒有LDAP或CORBA服務(wù)器,也就無法啟動這兩種JNDI服務(wù),DNS用于查域名的,以后再研究,唯一可以在main()中啟動的就是基于RMI的JNDI服務(wù)。
現(xiàn)在我們就在main()中啟動基于RMI的JNDI服務(wù)并且綁一個Date對象到JNDI上:
LocateRegistry.createRegistry(1099);
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
System.setProperty(Context.PROVIDER_URL, "rmi://localhost:1099");
InitialContext ctx = new InitialContext();
class RemoteDate extends Date implements Remote {};
ctx.bind("java:comp/env/systemStartTime", new RemoteDate());
ctx.close();
注意,我直接把JNDI的相關(guān)參數(shù)放入了System.properties中,這樣,后面的代碼如果要查JNDI,直接new InitialContext()就可以了,否則,你又得寫Hashtable env = ...
在RMI中綁JNDI的限制是,綁定的對象必須是Remote類型,所以就自己擴展一個。
其實JNDI還有兩個Context.SECURITY_PRINCIPAL和Context.SECURITY_CREDENTIAL,如果訪問JNDI需要用戶名和口令,這兩個也要提供,不過一般用不上。
在后面的代碼中查詢就簡單了:
InitialContext ctx = new InitialContext();
Date startTime = (Date) ctx.lookup("java:comp/env/systemStartTime");
在SUN的JNDI tutorial中的例子用的com.sun.jndi.fscontext.RefFSContextFactory類,但是我死活在JDK 5中沒有找到這個類,也就是NoClassDefFoundError,他也不說用的哪個擴展包,我也懶得找了。
posted @
2006-10-11 14:55 xzc 閱讀(1868) |
評論 (0) |
編輯 收藏
摘要:
本文闡述了怎么使用DBMS存儲過程。我闡述了使用存儲過程的基本的和高級特性,比如返回ResultSet。本文假設(shè)你對DBMS和JDBC已經(jīng)非常熟悉,也假設(shè)你能夠毫無障礙地閱讀其它語言寫成的代碼(即不是Java的語言),但是,并不要求你有任何存儲過程的編程經(jīng)歷。
本文闡述了怎么使用DBMS存儲過程。我闡述了使用存儲過程的基本的和高級特性,比如返回ResultSet。本文假設(shè)你對DBMS和JDBC已經(jīng)非常熟悉,也假設(shè)你能夠毫無障礙地閱讀其它語言寫成的代碼(即不是Java的語言),但是,并不要求你有任何存儲過程的編程經(jīng)歷。
存儲過程是指保存在數(shù)據(jù)庫并在數(shù)據(jù)庫端執(zhí)行的程序。你可以使用特殊的語法在Java類中調(diào)用存儲過程。在調(diào)用時,存儲過程的名稱及指定的參數(shù)通過JDBC連接發(fā)送給DBMS,執(zhí)行存儲過程并通過連接(如果有)返回結(jié)果。
使用存儲過程擁有和使用基于EJB或CORBA這樣的應(yīng)用服務(wù)器一樣的好處。區(qū)別是存儲過程可以從很多流行的DBMS中免費使用,而應(yīng)用服務(wù)器大都非常昂貴。這并不只是許可證費用的問題。使用應(yīng)用服務(wù)器所需要花費的管理、編寫代碼的費用,以及客戶程序所增加的復(fù)雜性,都可以通過DBMS中的存儲過程所整個地替代。
你可以使用Java,Python,Perl或C編寫存儲過程,但是通常使用你的DBMS所指定的特定語言。Oracle使用PL/SQL,PostgreSQL使用pl/pgsql,DB2使用Procedural SQL。這些語言都非常相似。在它們之間移植存儲過程并不比在Sun的EJB規(guī)范不同實現(xiàn)版本之間移植Session Bean困難。并且,存儲過程是為嵌入SQL所設(shè)計,這使得它們比Java或C等語言更加友好地方式表達數(shù)據(jù)庫的機制。
因為存儲過程運行在DBMS自身,這可以幫助減少應(yīng)用程序中的等待時間。不是在Java代碼中執(zhí)行4個或5個SQL語句,而只需要在服務(wù)器端執(zhí)行1個存儲過程。網(wǎng)絡(luò)上的數(shù)據(jù)往返次數(shù)的減少可以戲劇性地優(yōu)化性能。
使用存儲過程簡單的老的JDBC通過CallableStatement類支持存儲過程的調(diào)用。該類實際上是PreparedStatement的一個子類。假設(shè)我們有一個poets數(shù)據(jù)庫。數(shù)據(jù)庫中有一個設(shè)置詩人逝世年齡的存儲過程。下面是對老酒鬼Dylan Thomas(old soak Dylan Thomas,不指定是否有關(guān)典故、文化,請批評指正。譯注)進行調(diào)用的詳細代碼:
try{
int age = 39;
String poetName = "dylan thomas";
CallableStatement proc = connection.prepareCall("{ call set_death_age(?, ?) }");
proc.setString(1, poetName);
proc.setInt(2, age);
cs.execute();
}catch (SQLException e){ // ....}
傳給prepareCall方法的字串是存儲過程調(diào)用的書寫規(guī)范。它指定了存儲過程的名稱,?代表了你需要指定的參數(shù)。
和JDBC集成是存儲過程的一個很大的便利:為了從應(yīng)用中調(diào)用存儲過程,不需要存根(stub)類或者配置文件,除了你的DBMS的JDBC驅(qū)動程序外什么也不需要。
當這段代碼執(zhí)行時,數(shù)據(jù)庫的存儲過程就被調(diào)用。我們沒有去獲取結(jié)果,因為該存儲過程并不返回結(jié)果。執(zhí)行成功或失敗將通過例外得知。失敗可能意味著調(diào)用存儲過程時的失敗(比如提供的一個參數(shù)的類型不正確),或者一個應(yīng)用程序的失敗(比如拋出一個例外指示在poets數(shù)據(jù)庫中并不存在“Dylan Thomas”)
結(jié)合SQL操作與存儲過程映射Java對象到SQL表中的行相當簡單,但是通常需要執(zhí)行幾個SQL語句;可能是一個SELECT查找ID,然后一個INSERT插入指定ID的數(shù)據(jù)。在高度規(guī)格化(符合更高的范式,譯注)的數(shù)據(jù)庫模式中,可能需要多個表的更新,因此需要更多的語句。Java代碼會很快地膨脹,每一個語句的網(wǎng)絡(luò)開銷也迅速增加。
將這些SQL語句轉(zhuǎn)移到一個存儲過程中將大大簡化代碼,僅涉及一次網(wǎng)絡(luò)調(diào)用。所有關(guān)聯(lián)的SQL操作都可以在數(shù)據(jù)庫內(nèi)部發(fā)生。并且,存儲過程語言,例如PL/SQL,允許使用SQL語法,這比Java代碼更加自然。下面是我們早期的存儲過程,使用Oracle的PL/SQL語言編寫:
create procedure set_death_age(poet VARCHAR2, poet_age NUMBER)
poet_id NUMBER;
begin SELECT id INTO poet_id FROM poets WHERE name = poet;
INSERT INTO deaths (mort_id, age) VALUES (poet_id, poet_age);
end set_death_age;
很獨特?不。我打賭你一定期待看到一個poets表上的UPDATE。這也暗示了使用存儲過程實現(xiàn)是多么容易的一件事情。set_death_age幾乎可以肯定是一個很爛的實現(xiàn)。我們應(yīng)該在poets表中添加一列來存儲逝世年齡。Java代碼中并不關(guān)心數(shù)據(jù)庫模式是怎么實現(xiàn)的,因為它僅調(diào)用存儲過程。我們以后可以改變數(shù)據(jù)庫模式以提高性能,但是我們不必修改我們代碼。
下面是調(diào)用上面存儲過程的Java代碼:
public static void setDeathAge(Poet dyingBard, int age) throws SQLException{
Connection con = null;
CallableStatement proc = null;
try {
con = connectionPool.getConnection();
proc = con.prepareCall("{ call set_death_age(?, ?) }");
proc.setString(1, dyingBard.getName());
proc.setInt(2, age);
proc.execute();
}
finally {
try { proc.close(); }
catch (SQLException e) {}
con.close();
}
}
為了確保可維護性,建議使用像這兒這樣的static方法。這也使得調(diào)用存儲過程的代碼集中在一個簡單的模版代碼中。如果你用到許多存儲過程,就會發(fā)現(xiàn)僅需要拷貝、粘貼就可以創(chuàng)建新的方法。因為代碼的模版化,甚至也可以通過腳本自動生產(chǎn)調(diào)用存儲過程的代碼。
Functions存儲過程可以有返回值,所以CallableStatement類有類似getResultSet這樣的方法來獲取返回值。當存儲過程返回一個值時,你必須使用registerOutParameter方法告訴JDBC驅(qū)動器該值的SQL類型是什么。你也必須調(diào)整存儲過程調(diào)用來指示該過程返回一個值。
下面接著上面的例子。這次我們查詢Dylan Thomas逝世時的年齡。這次的存儲過程使用PostgreSQL的pl/pgsql:
create function snuffed_it_when (VARCHAR) returns integer ''declare
poet_id NUMBER;
poet_age NUMBER;
begin
--first get the id associated with the poet.
SELECT id INTO poet_id FROM poets WHERE name = $1;
--get and return the age.
SELECT age INTO poet_age FROM deaths WHERE mort_id = poet_id;
return age;
end;'' language ''pl/pgsql'';
另外,注意pl/pgsql參數(shù)名通過Unix和DOS腳本的$n語法引用。同時,也注意嵌入的注釋,這是和Java代碼相比的另一個優(yōu)越性。在Java中寫這樣的注釋當然是可以的,但是看起來很凌亂,并且和SQL語句脫節(jié),必須嵌入到Java String中。
下面是調(diào)用這個存儲過程的Java代碼:
connection.setAutoCommit(false);
CallableStatement proc = connection.prepareCall("{ ? = call snuffed_it_when(?) }");
proc.registerOutParameter(1, Types.INTEGER);
proc.setString(2, poetName);
cs.execute();
int age = proc.getInt(2);
如果指定了錯誤的返回值類型會怎樣?那么,當調(diào)用存儲過程時將拋出一個RuntimeException,正如你在ResultSet操作中使用了一個錯誤的類型所碰到的一樣。
復(fù)雜的返回值關(guān)于存儲過程的知識,很多人好像就熟悉我們所討論的這些。如果這是存儲過程的全部功能,那么存儲過程就不是其它遠程執(zhí)行機制的替換方案了。存儲過程的功能比這強大得多。
當你執(zhí)行一個SQL查詢時,DBMS創(chuàng)建一個叫做cursor(游標)的數(shù)據(jù)庫對象,用于在返回結(jié)果中迭代每一行。ResultSet是當前時間點的游標的一個表示。這就是為什么沒有緩存或者特定數(shù)據(jù)庫的支持,你只能在ResultSet中向前移動。
某些DBMS允許從存儲過程中返回游標的一個引用。JDBC并不支持這個功能,但是Oracle、PostgreSQL和DB2的JDBC驅(qū)動器都支持在ResultSet上打開到游標的指針(pointer)。
設(shè)想列出所有沒有活到退休年齡的詩人,下面是完成這個功能的存儲過程,返回一個打開的游標,同樣也使用PostgreSQL的pl/pgsql語言:
create procedure list_early_deaths () return refcursor as ''declare
toesup refcursor;
begin
open toesup for SELECT poets.name, deaths.age FROM poets, deaths -- all entries in deaths are for poets. -- but the table might become generic.
WHERE poets.id = deaths.mort_id AND deaths.age < 60;
return toesup;
end;'' language ''plpgsql'';
下面是調(diào)用該存儲過程的Java方法,將結(jié)果輸出到PrintWriter:
PrintWriter:
static void sendEarlyDeaths(PrintWriter out){
Connection con = null;
CallableStatement toesUp = null;
try {
con = ConnectionPool.getConnection();
// PostgreSQL needs a transaction to do this... con.
setAutoCommit(false); // Setup the call.
CallableStatement toesUp = connection.prepareCall("{ ? = call list_early_deaths () }");
toesUp.registerOutParameter(1, Types.OTHER);
toesUp.execute();
ResultSet rs = (ResultSet) toesUp.getObject(1);
while (rs.next()) {
String name = rs.getString(1);
int age = rs.getInt(2);
out.println(name + " was " + age + " years old.");
}
rs.close();
}
catch (SQLException e) { // We should protect these calls. toesUp.close(); con.close();
}
}
因為JDBC并不直接支持從存儲過程中返回游標,我們使用Types.OTHER來指示存儲過程的返回類型,然后調(diào)用getObject()方法并對返回值進行強制類型轉(zhuǎn)換。
這個調(diào)用存儲過程的Java方法是mapping的一個好例子。Mapping是對一個集上的操作進行抽象的方法。不是在這個過程上返回一個集,我們可以把操作傳送進去執(zhí)行。本例中,操作就是把ResultSet打印到一個輸出流。這是一個值得舉例的很常用的例子,下面是調(diào)用同一個存儲過程的另外一個方法實現(xiàn):
public class ProcessPoetDeaths{
public abstract void sendDeath(String name, int age);
}
static void mapEarlyDeaths(ProcessPoetDeaths mapper){
Connection con = null;
CallableStatement toesUp = null;
try {
con = ConnectionPool.getConnection();
con.setAutoCommit(false);
CallableStatement toesUp = connection.prepareCall("{ ? = call list_early_deaths () }");
toesUp.registerOutParameter(1, Types.OTHER);
toesUp.execute();
ResultSet rs = (ResultSet) toesUp.getObject(1);
while (rs.next()) {
String name = rs.getString(1);
int age = rs.getInt(2);
mapper.sendDeath(name, age);
}
rs.close();
} catch (SQLException e) { // We should protect these calls. toesUp.close();
con.close();
}
}
這允許在ResultSet數(shù)據(jù)上執(zhí)行任意的處理,而不需要改變或者復(fù)制獲取ResultSet的方法:
static void sendEarlyDeaths(final PrintWriter out){
ProcessPoetDeaths myMapper = new ProcessPoetDeaths() {
public void sendDeath(String name, int age) {
out.println(name + " was " + age + " years old.");
}
};
mapEarlyDeaths(myMapper);
}
這個方法使用ProcessPoetDeaths的一個匿名實例調(diào)用mapEarlyDeaths。該實例擁有sendDeath方法的一個實現(xiàn),和我們上面的例子一樣的方式把結(jié)果寫入到輸出流。當然,這個技巧并不是存儲過程特有的,但是和存儲過程中返回的ResultSet結(jié)合使用,是一個非常強大的工具。
結(jié)論存儲過程可以幫助你在代碼中分離邏輯,這基本上總是有益的。這個分離的好處有:
• 快速創(chuàng)建應(yīng)用,使用和應(yīng)用一起改變和改善的數(shù)據(jù)庫模式。
• 數(shù)據(jù)庫模式可以在以后改變而不影響Java對象,當我們完成應(yīng)用后,可以重新設(shè)計更好的模式。
• 存儲過程通過更好的SQL嵌入使得復(fù)雜的SQL更容易理解。
• 編寫存儲過程比在Java中編寫嵌入的SQL擁有更好的工具--大部分編輯器都提供語法高亮!
• 存儲過程可以在任何SQL命令行中測試,這使得調(diào)試更加容易。
并不是所有的數(shù)據(jù)庫都支持存儲過程,但是存在許多很棒的實現(xiàn),包括免費/開源的和非免費的,所以移植并不是一個問題。Oracle、PostgreSQL和DB2都有類似的存儲過程語言,并且有在線的社區(qū)很好地支持。
存儲過程工具很多,有像TOAD或TORA這樣的編輯器、調(diào)試器和IDE,提供了編寫、維護PL/SQL或pl/pgsql的強大的環(huán)境。
存儲過程確實增加了你的代碼的開銷,但是它們和大多數(shù)的應(yīng)用服務(wù)器相比,開銷小得多。
posted @
2006-10-11 14:22 xzc 閱讀(222) |
評論 (0) |
編輯 收藏
近日學(xué)習了一下AJAX,照做了幾個例子,感覺比較新奇。
第一個就是自動完成的功能即Autocomplete,具體的例子可以在這里看:http://www.b2c-battery.co.uk
在Search框內(nèi)輸入一個產(chǎn)品型號,就可以看見效果了。
這里用到了一個開源的代碼:AutoAssist ,有興趣的可以看一下。
以下為代碼片斷:
index.htm
<script type="text/javascript" src="javascripts/prototype.js"></script>
<script type="text/javascript" src="javascripts/autoassist.js"></script>
<link rel="stylesheet" type="text/css" href="styles/autoassist.css"/>
<div>
<input type="text" name="keyword" id="keyword"/>
<script type="text/javascript">
Event.observe(window, "load", function() {
?var aa = new AutoAssist("keyword", function() {
??return "forCSV.php?q=" + this.txtBox.value;
?});
});
</script>
</div>
不知道為什么不能用keywords做文本框的名字,我試了很久,后來還是用keyword,搞得還要修改原代碼。
forCSV.php
<?php
? $keyword = $_GET['q'];
? $count = 0;
? $handle = fopen("products.csv", "r");
? while (($data = fgetcsv($handle, 1000)) !== FALSE) {
??? if (preg_match("/$keyword/i", $data[0])) {
????? if ($count++ > 10) { break; }
?>
????? <div onSelect="this.txtBox.value='<?php echo $data[0]; ?>';">
??????? <?php echo $data[0]; ?>
????? </div>
<?php
??? }
? }
? fclose($handle);
? if ($count == 0) {
?>
? : (, nothing found.
<?php
? }
?>
原來的例子中的CSV文件是根據(jù)\t來分隔的,我們也可以用空格或其它的來分隔,這取決于你的數(shù)據(jù)結(jié)構(gòu)。
當然你也可以不讀文件,改從數(shù)據(jù)庫里讀資料,就不再廢話了。
效果圖如下:
?
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=635858
posted @
2006-10-08 14:12 xzc 閱讀(402) |
評論 (1) |
編輯 收藏