一、為什么要配置dwr.xml
要理解dwr.xml的配置首先要理解DWR的基本功能。DWR其功能的強大在于它可以用一種前所未有的便利方式將前端頁面中的js與后端服務器中的java程序進行直接的轉換。比如,它可以將java程序中的某個XxxBus轉換成js中的一個對象,并將XxxBus中的某個方法如getXXX()轉換成js中的一個名為XxxBus.getXXX()的function。同時,它可以將java中的值對象或其它數據包轉換成js中的對象。理解這一點非常重要。配置文件dwr.xml就是讓你告訴DWR哪些Bus你需要在頁面中調用,以及你需要調用這些Bus中的哪些方法,哪些方法你不需要調用。一旦告訴了DWR這些信息,你似乎就可以在頁面中像寫java程序一樣的直接調用Bus,而跳過了MVC層(如struts中的action和actionform)以及MVC層的繁瑣配置與編程。為了幫助你理解這一點我舉一個例子,已經清楚的朋友可以跳過。
假如我在項目的BUS層寫了一個類叫DepartmentBus并在這個類中定義了一個方法getDepartment(String id),那么我們先把這個類和它的方法配置到dwr.xml中(如何配置我后面再講)。當DWR啟動的時候,它會將java中的DepartmentBus及其方法getDepartment(String id)轉換成js中的DepartmentBus及其方法getDepartment(String id)。更具體地說,它會動態產生一個叫DepartmentBus.js的文件(這個文件可以在測試狀態下從“項目地址端口/dwr/test/DepartmentBus”中看到)。在這個文件中,會創建一個DepartmentBus的對象,一個叫DepartmentBus.fincDepartment(p0, callback)的函數。如果你在頁面中引入了DepartmentBus.js文件,你就可以在該頁面的中調用DepartmentBus.fincDepartment(p0, callback)方法,DWR就可以通過Ajax的方式直接去調用服務器端的DepartmentBus中的fincDepartment(String id)。同理,如果DepartmentBus中還有其它的函數,如createDepartment(Department vo)、updateDepartment(Department vo)等,DWR也會做如此的轉換。
前面我們提到,DWR可以將服務器端java中的對象及其方法轉換成客戶端的js中的對象及其方法,一個棘手的問題就出現了。在java中的許多方法都是將某個對象作為它參數或返回值。這些對象往往都是一個個的JOPO,即它們有許多的屬性,其中包含了需要傳遞的數據(最典型的就是值對象)。當js在調用java的某個方法時需要提供這個對象參數,同時在調用完成時需要將對象返回值以js可以接受的方式返回。怎樣做到這一點呢?最簡單的方式當然是讓DWR再做一次轉換,將這些java對象轉換成js的對象,或者將js的對象轉換成java對象。Ok,經過DWR這樣的兩次轉換,我們就可以在頁面與后臺間自由地進行函數調用和數據交換。
二、如何配置dwr.xml文件
講到這里,我們現在重新回到dwr.xml文件。前面我們提到,如果你需要在頁面中調用服務器端的某個類中的方法,你需要在dwr.xml中注冊這個類及其方法,注冊方法如下:
- <create creator="spring" javascript="DepartmentBus" scope="script">
- <param name="beanName" value="departmentBus"/>
- create>
Creator是創建這個對象所使用的構建器,如果你希望使用傳統的new方法就寫成new,如果你希望使用spring來創建則寫成spring。當然也有其它的創建方法,有哪些方法呢?你可以打開dwr.jar,在org.directwebremoting包中找到一個dwr.xml的文件。在該文件init下的create中可以看到。
Javascript是用于你在js中調用這個對象時使用什么名稱,強烈建議你使用首字母大寫,這樣在頁面中很容易看出這是一個與后臺對應的對象。如果你使用了spring并且在creator中選擇了spring,那么你需要一個叫beanName的參數,而其值就是在spring配置文件中配置的beanName,如departmentBus。另外2個非常有用的參數是exclude和include,exclude可以禁止頁面調用后臺的某個或某些方法,具體的寫法是在create中加入:- <exclude method="createDepartment"/>
method部分寫的是這些方法不帶括號的方法名,如果有多個就寫多行exclude;include則規定頁面只能調用某些方法。
在dwr.xml中注冊了需要調用的對象及其方法以后,你應當注冊其所有方法的參數和返回值所涉及的對象,注冊方法如下:
- <convert match="com.htxx.demo.datasource1.model.Department"
- javascript="Department" converter="hibernate3"/>
match用于告訴DWR你將需要把java中的哪些類轉換成js。你可以寫成com.htxx.demo.model.*,但我并不推薦大家這樣使用。為什么呢?如果你像前面那樣一個一個地注冊對象,則就可以在頁面使用這個的語句初始化一個對象:
var dep = new Department();
如果你使用后一種方法去批量注冊對象,那么你就不能這樣初始化這個對象而只能這樣手動注冊:
Var dep = {departmentId:null, departmentName:null, ……};
我推薦大家采用第一種方法的好處可以在我后面寫的《DWR幫助說明-如何編寫通用的單行編輯框》充分展現出來。但采用這種方法在DWR的現有版本中似乎還有點兒BUG,如何解決這個BUG我也將在后面的《DWR幫助說明-dwr的bug及其解決方法》中解答。
Javascript用于說明你在頁面中使用這個對象的名稱,也強烈建議大家使用首字母大寫。Converter用于告訴DWR用什么DWR的類來執行轉換,常用的轉換器有bean、object、hibernate2、hibernate3等。DWR有哪些轉換器可以在dwr.jar的dwr.xml(該文件的位置見前文)中找到。我需要強調的是,如果朋友們使用了hibernate,那么你需要將需要使用的所有值對象都通過轉換器注冊。但是我在網上看見很多朋友都使用bean轉換器來注冊。如果你使用bean來轉換值對象,在運行程序的時候會出現很多問題(這些問題我就不詳述了),同時還會出現效率的問題,因為DWR會將該值對象的所有屬性,及其這些屬性的所有屬性,所有屬性的屬性,都以窮舉的方式取出來。熟悉hibernate的朋友應當馬上明白這樣將是數據庫操作的一個災難。如果你使用hibernate2或hibernate3作為轉換器將不會發生這樣的事,同時,hibernate3還較好地解決了延遲查詢的問題,但DWR官方建議我們使用hibernate的openSessionInViewFilter,這我也不再詳述,不清楚的朋友可以查閱hibernate3的幫助文檔。但另一個問題我不得不提,DWR在使用延遲查詢的時候其實還是有問題的。譬如有一個值對象Employee包含一個Department的屬性,根據延遲查詢的規則,在get某個Employee時,屬性Department不會馬上裝載,即使執行getDepartment()也不會裝載。必須到真正對這個Department操作的時候才會裝載。既然如此,問題就來了,我們使用DWR執行查詢的時候,常常是真正到頁面才會讀取Department,這時已經是脫離服務器端到頁面端了而不能再得到Department。這個問題怎么辦呢,最好的辦法是在服務器端就提前裝載頁面需要使用的屬性,因為作為開發者他肯定知道哪些屬性要在客戶端使用,哪些屬性不需要。至于如何在服務器端就提前裝載,感興趣的朋友在我的示例中看到。另外一個需要提的是,與creator一樣,轉換器也可以一樣地設置exclude和include參數。但是與creator不同的是,它們說明DWR在轉換對象的時候需要轉換或不轉換某些屬性。這個參數對于hibernate的一對一關聯非常重要。在hibernate中一對一關聯是不做延遲查詢的,假如有一個值對象Employee與值對象Address是一對一關聯,那么Employee中有Address的屬性,而Address中有Employee的屬性。由于一對一關聯不做延遲查詢,當DWR在轉換一個Employee是會裝載它的屬性Address,然后在裝載Address的時候,又會去裝載Address中的Employee屬性。如此這樣,就會形成一個死循環,最后以堆棧溢出告終。解決這個問題的辦法就是禁掉Address中的Employee屬性,避免產生死循環。具體寫法如下:
- <convert match="com.htxx.demo.model.Address" javascript="Address" converter="hibernate3">
- <param name="exclude" value="employee"/>
- </convert>
Value部分是需要轉換的屬性,如果有多個則用逗號隔開就可以了。