不斷發(fā)展的 Java 編程語言和 Sun 公司的 J2EE 規(guī)范使得遵守各類準則的軟件開發(fā)者們能夠創(chuàng)建出分布式計算應(yīng)用程序,這些應(yīng)用程序在以前只能通過相關(guān)屬性工具才可實現(xiàn)。這樣,當某些開發(fā)團體要選擇在 Java 平臺中實現(xiàn)新系統(tǒng)時,其他團體就會通過另外的技術(shù)來創(chuàng)建、提高并維護應(yīng)用程序,然后將它們集成到已有的各類分布式應(yīng)用程序中去。這種情形就引起了互操作性的競爭。新應(yīng)用程序與舊應(yīng)用程序如何交互呢?答案就是:Web 服務(wù)。Web 服務(wù)是程序設(shè)計新的圣杯。它們能夠共享并協(xié)調(diào)分散的各類計算資源。
在本文中,您將了解實現(xiàn)此目的的一種方法。您將看到如何在與 Web 服務(wù)相結(jié)合的開放源碼 Structs 框加的基礎(chǔ)上來構(gòu)建應(yīng)用程序。在開始之前您必須了解一些 J2EE 與 Web 服務(wù)的相關(guān)知識。這里,我將簡要地介紹下 Structs 架構(gòu)與模型-視圖-控制器(MVC)。
MVC 模式:分離開發(fā)角色
MVC 設(shè)計模式很清楚的劃定了程序員與設(shè)計者的角色界限。換句話說,從商業(yè)邏輯上拆解了數(shù)據(jù)。這種模式是讓設(shè)計者集中于設(shè)計應(yīng)用程序的顯示部分,而開發(fā)者則集中于開發(fā)驅(qū)動應(yīng)用程序功能所需的組件。
MVC 模式有好幾種變異,不過它們都是基于相同的基礎(chǔ)結(jié)構(gòu):應(yīng)用程序的數(shù)據(jù)模型 (Model),代碼顯示(View),以及程序控制邏輯( Controller) 是存在其中的獨立但能相互間通信的組件。模型組件描述并處理應(yīng)用程序數(shù)據(jù)。視圖指的是用戶接口;它反映的是模型數(shù)據(jù)并把它遞交給用戶??刂破魇菍⒁晥D上的行為(例如,按下 Submit 按鈕)映射到模型上的操作(例如,檢索用戶詳細信息)。模型更新后,視圖也被更新,用戶就能夠完成更多行為。MVC 模式使代碼易懂而且使代碼更容易重用;另外,在很多工程中視圖經(jīng)常要被更新,MVC 模式將模型和控制器與這些所做的更改獨立開來。
圖 1 MVC 模式概要
Struts:基于 MVC 的堅固框架
Struts 是 MVC 模式基礎(chǔ)上構(gòu)建 Web 應(yīng)用程序的一種開放源碼框加。Structs 鼓勵在 MVC 模式上構(gòu)建應(yīng)用程序而且提供大多數(shù) Web 應(yīng)用程序所共有的服務(wù)。
在 Struts 應(yīng)用程序中,您可以構(gòu)建模型層,這樣業(yè)務(wù)邏輯與數(shù)據(jù)檢索邏輯重用就很容易了。這層負責運行應(yīng)用程序的業(yè)務(wù)邏輯,獲取相關(guān)數(shù)據(jù)(例如,運行 SQL 命令或者讀取平面文件)。
Struts 鼓勵在模型-視圖-控制器設(shè)計范例基礎(chǔ)上構(gòu)建應(yīng)用程序。Structs 提供自己的控制器組件(ActionController 類)并與其他技術(shù)相結(jié)合來提供模型與視圖。對于模型(Model 類), Struts 能與任何標準的數(shù)據(jù)訪問技術(shù)相結(jié)合,包括 EJB、JDBC 以及 Object-Relational Bridge。對于視圖(ActionForm 類),Struts 在 JSP 環(huán)境以及其他描述系統(tǒng)中運行的很好。圖 2 闡明了基于 Structs 應(yīng)用程序的邏輯流程。
圖 2. Struts 應(yīng)用程序的邏輯流
圖 3. 簡單粗糙的 Web 服務(wù)體系結(jié)構(gòu)
這樣的一種 Web 服務(wù)可以很容易從已有的業(yè)務(wù)組件中開發(fā)出來。然而,它有很多的缺點:對用戶沒有單個簽名點,提供者與訂閱者連接不夠緊,業(yè)務(wù)邏輯沒有重用。簡而言之,對于一個連貫的解決方案來說這并不是一個非常好的體系結(jié)構(gòu)。
在 MVC 模式基礎(chǔ)上來實現(xiàn) Web 服務(wù)解決方案會更好點。在后續(xù)部分,您將看到如何使用 Structs 來做到這一點。我將通過 WSManager 層來詳述現(xiàn)有的 Structs 架構(gòu),這一層展示了采用 Web 服務(wù)的模型服務(wù)方法。
采用 Struts 應(yīng)用程序的 Web 服務(wù)
在以后的開發(fā)工作中您可以擴大構(gòu)建得比較好的 Structs 應(yīng)用程序來支持 Web 服務(wù)。前面講過,Structs 架構(gòu)清晰地區(qū)分開了視圖、控制器以及模型。模型包含所有必須的業(yè)務(wù)邏輯,從存儲數(shù)據(jù)倉庫中檢索數(shù)據(jù)。您可以構(gòu)建一個簡單的 Web 服務(wù)層(稱為 WSManager 層)這樣的模型可以提供 Web 服務(wù)也可以訂購 Web 服務(wù)。使用這種體系結(jié)構(gòu)的應(yīng)用程序?qū)⒒诮M件開發(fā)的最佳點與萬維網(wǎng)結(jié)合在了一起,如 圖 4所示。
圖 4. 采用 Web 服務(wù)的 Struts 應(yīng)用程序
下面的部分要討論的是這種體系結(jié)構(gòu)中不同組件的詳細信息,特別要密切注意 WSManager 層,因為它是此體系結(jié)構(gòu)中真正新加的部分。
WSManager 也可以生成響應(yīng);這個過程由方法調(diào)用返回值的簡單構(gòu)造所組成。WSManager 中保留這樣的功能,您可以通過緩存數(shù)據(jù)從而避免重復(fù)訪問模型服務(wù)層。您還可以集中管理響應(yīng)聚集以及 XML 文檔轉(zhuǎn)換,如果您要返回給調(diào)度者的文檔必須遵守與內(nèi)部 schema 不同的 schema 時,這一點就顯得尤其重要。
WSManager 處理所有新來的 SOAP 請求并授權(quán)它們連接到出現(xiàn)在模型服務(wù)中的業(yè)務(wù)邏輯。如果模型服務(wù)是作為一個 EJB 層來實現(xiàn),那您一樣可以在 EJB 層中通過 Session Façade 設(shè)計模式來實現(xiàn)。如果您采用這種模式執(zhí)行 WSManager ,您將獲得很多好處,因為 WSManager 會:
作為初始聯(lián)系點來管理請求與服務(wù)
調(diào)用安全服務(wù),包括身份驗證與授權(quán)使用,從而避免任何重復(fù)的層訪問。
授權(quán)業(yè)務(wù)處理(采用由 Structs 應(yīng)用程序所使用的模型服務(wù))
在 WSManager 層緩存數(shù)據(jù)避免任何不必要的數(shù)據(jù)庫訪問。
發(fā)布者:展示 Web 服務(wù)
在 WSManager 類中執(zhí)行的每一個公共方法都將作為一種 Web 服務(wù)發(fā)布出來。換句話說,您要為這些類發(fā)布一種 Web 服務(wù)描述 。Web 服務(wù)描述是由服務(wù)的 Web 服務(wù)描述語言(Web Services Description Language,WSDL)描述與由它所引用的任何 XML schema 所組成。(WSDL 是描述服務(wù)的標準語言。)
您可以在公共注冊表或在企業(yè)內(nèi)的公司注冊表上發(fā)布 Web 服務(wù)描述。您還可以發(fā)布 XML schema,這些是由相同公司或公共存儲器上的 Web 服務(wù)所定義的。 Java Web 服務(wù)客戶端采用 JAXR 應(yīng)用編程接口來查詢公司或公共注冊表上的服務(wù)描述。
如果您的客戶都是特定的合作伙伴,那您就不需要使用注冊表了。不過,您可以在您的應(yīng)用程序的 Web 層或者在具備適當安全保護的熟知位置來發(fā)布您的 Web 服務(wù)描述( WSDL 和 XML schemas)。例如,假想有一個轉(zhuǎn)售者的客戶應(yīng)用程序,他與某個特殊廠商有協(xié)定。客戶應(yīng)用程序已經(jīng)在廠商開發(fā)時間中靜態(tài)地 與 Web 服務(wù)結(jié)合在一起了。只有授權(quán)的團體才可以查詢 XML schema 或者從 Web 層檢索服務(wù)描述來生成客戶端代碼。您應(yīng)該在 WSManager 層中執(zhí)行有效客戶的身份驗證和授權(quán)使用。
訂購者:使用 Web 服務(wù)
應(yīng)用程序可以利用在公共注冊表或者企業(yè)內(nèi)部中已存的 Web 服務(wù)。 WSManager 有解析必要的 WSDL 文件的方法并且調(diào)用相關(guān)的操作返回一個值。Structs 控制器在 WSManager 類中調(diào)用相關(guān)方法來使用特定的 Web 服務(wù)。數(shù)據(jù)作為預(yù)定義的數(shù)據(jù)訪問對象在 WSManager 和 Structs 控制器之間來回傳送。訪問 Web 服務(wù)時所發(fā)生的所有異常都將在 WSManager 中列舉出來并且傳回到 Struts ActionController.
服務(wù)請求者要通過使用服務(wù)代理來搜索 Web 服務(wù);如果找到它所想要使用的 Web 服務(wù),那么它就要與服務(wù)提供者建立一個合約才能享受這個服務(wù),業(yè)務(wù)也是如此。
WSManager 使用 WSDL 文檔、服務(wù)器名字、端口名、操作名以及包括 Java 原始類型、Java 數(shù)組、Java 對象 或者與 XML 文檔等所有必需的請求參數(shù)一起來訂購一個 Web 服務(wù)。
如果是在 UDDI 注冊表中發(fā)布目標 Web 服務(wù),那么所有基于 Structs 的應(yīng)用程序都能使用像 XMethods (請參閱參考資料)這樣的代理服務(wù)來訂購它。在執(zhí)行完所請求的操作后,供應(yīng)者 Web 服務(wù)返回所期望的值。 WSManager 可以更改返回值使得它與應(yīng)用程序所期待的 schema 相匹配;它也可以在應(yīng)用程序要求基礎(chǔ)上修改結(jié)果。從 WSManager 收到結(jié)果以后,Struts ActionController 能夠處理結(jié)果并傳送給相關(guān)視圖,或者還能調(diào)用相關(guān)的模型服務(wù)來完成深層處理。
錯誤處理
所有的錯誤都是在 WSManager 層中處理的;這就消除了不必要的服務(wù)器開銷。如果模型服務(wù)是當作 EJB 層來執(zhí)行的,那么其性能會得到非常顯著地增強。
在充當供應(yīng)者角色時, WSManager 拋出所有如 SOAPFaultException 這樣的異常。它還可以檢查新來的請求并且拋出所有缺少命令域的異常。您可以創(chuàng)建一個類來跟蹤并在數(shù)據(jù)倉庫中記錄這些錯誤,以后可以做作參考。
在充當訂購者角色時, WSManager 捕捉到由服務(wù)供應(yīng)者所拋出的所有 SOAP 異常并且將它們更改為 WSManager 所要求的格式。您可以記錄下所拋出的錯誤作為以后參考之用。如果需要,那么在需要時候也能夠校驗出響應(yīng)值并且作為異常拋出。您可以創(chuàng)建一個類來記錄這些異常,作為以后參考之用。 WSManager 能夠驗證響應(yīng)值而且可以作為一個異常將它拋出。
審核
在充當供應(yīng)者角色時, WSManager 可以記錄下詳細信息,將來可以做為審核。使用這些信息有很多用途,像:
在所接收到的大量采樣點基礎(chǔ)上登記客戶端。
收集數(shù)據(jù)用作市場目的
決定應(yīng)用程序是否需要更新
鑒別并捕捉流氓用戶。
緩存
Web 服務(wù)的客戶往往要比一般的客戶端-服務(wù)器體系結(jié)構(gòu)中的客戶要多些;因此在 Web 服務(wù)體系結(jié)構(gòu)中,客戶端就要做更多的工作,比如緩存。Web 服務(wù)正確使用數(shù)據(jù)緩存就可以實現(xiàn)其最大的性能。當服務(wù)的請求信息主要是只讀的時候或者當那些信息按照比所要求的速率變化得還要慢時,您就應(yīng)該考慮要在 Web 服務(wù)中使用緩存了。
身份驗證與授權(quán)使用
您可以在 WSManager 層中執(zhí)行所有訂購者的身份驗證。所有想使用 Web 服務(wù)的客戶都要經(jīng)過這樣的身份驗證邏輯。您可以使用基本用戶身份驗證或者數(shù)字鑒定來實現(xiàn)此目的。
Struts 視圖
您要通過使用 JSP 技術(shù)來構(gòu)建基于 Structs 應(yīng)用程序的視圖部分。JSP 頁包含有靜態(tài) HTML 加上 動態(tài)內(nèi)容,這些內(nèi)容是基于對特別行為標簽說明(在頁面要求時)的。JSP 環(huán)境包括一套標準的行為標簽。另外,在自定義標簽庫中組織了一套標準的工具,開發(fā)者們可以使用這些工具來定義他們自己的標簽。
Structs 框架包含有擴展的自定義標簽庫,這個庫能幫助用戶界面國際化更為全面并能非常適度地與 ActionForm 組件相互作用。視圖層比較單薄,它不提供業(yè)務(wù)邏輯。Structs 視圖是通過 ActionForm 與 Structs 控制器相互作用的。
Struts ActionForm
ActionForms 只是一些 Java 類而已,它繼承了 Structs 所提供的 ActionForm 類,這些類中包含有 accessor 和 mutator 方法。 JSP 頁或者 Action 類都會調(diào)用這些方法來聚集或檢索數(shù)據(jù)。
模型服務(wù)
模型服務(wù)是作為一套 Java 類來執(zhí)行的。每個模型服務(wù)組件都會提供一套服務(wù),而這些組件結(jié)合起來同樣也提供一套普通服務(wù)。 ActionController 與 WSManager 類將數(shù)據(jù)當作預(yù)定義的數(shù)據(jù)訪問對象來回傳送。在處理過程中,ActionController 或者 WSManager 可以調(diào)用相關(guān)模型服務(wù)組件中所要求的方法。這些組件將所要求的數(shù)據(jù)以數(shù)據(jù)訪問對象的形式傳給模型服務(wù),模型服務(wù)執(zhí)行一切必須的商業(yè)邏輯處理然后從存儲數(shù)據(jù)倉庫中取出所需要的數(shù)據(jù)。模型服務(wù)組件聚集相關(guān)的預(yù)定義數(shù)據(jù)訪問對象,然后將它傳回給 ActionServlet 或者 WSManager 類。所有的錯誤或者確認信息都會通知給 ActionServlet 或 WSManager 層。
對于您的應(yīng)用程序,您應(yīng)該遵守以下的設(shè)計規(guī)則:
模型服務(wù)不能含有任何與視圖相關(guān)的代碼(例如,會話處理)。
所有的事務(wù)僅僅只能在 Action 或 WSManager 層中來處理。
模型服務(wù)只能被同一模型服務(wù)組件中其他的模型服務(wù)或者高層 Action 類所調(diào)用。
數(shù)據(jù)存儲層
數(shù)據(jù)存儲層由所有的存儲數(shù)據(jù)倉庫組成。例如,它可能包含有關(guān)系數(shù)據(jù)庫、平面文件或者甚至有 XML 文檔。
示例
我已經(jīng)附上了實現(xiàn)此處所討論的體系結(jié)構(gòu)的一個簡單示例。所有的示例代碼都包含在了 zip 文件中(請參閱下面的參考資料)。這個例子舉例說明了一個簡單的新聞 Portal。新聞內(nèi)容是從數(shù)據(jù)源(這里稱為 DataSource )傳送到 JSP 頁,同時信息內(nèi)容也要作為一個 Web 服務(wù)發(fā)布出來。這個 Portal 也可以從 StockQuote Web 服務(wù)中檢索最新的股票報價并顯示在同一個 JSP 頁中。
這個 zip 文件包含了所有的源代碼和 DataSource 的 SQL 腳本。它還包含有 Struts Action 和 ActionForm 類、模型服務(wù)類以及用來為發(fā)布者與訂購者顯示結(jié)果及其源代碼的 JSP 頁。它同時還包含有 WSDL 文件以及客戶端源代碼來訪問已發(fā)布的 NewsContent Web 服務(wù)。
這里我不再闡述示例代碼的詳細信息了;您想要發(fā)現(xiàn)它的復(fù)雜性,最好的途徑就是自己去試驗。開始時,我會向您說明應(yīng)用程序如何將信息內(nèi)容(存儲在 DataSource 中)和股票信息(從 Web 服務(wù)處獲得)遞交給 JSP 頁的。
JSP 頁被加載時,它就調(diào)用 Action 類,這個類在 MVC 設(shè)計模式中充當控制器角色。 Action 類通過傳遞預(yù)定義的數(shù)據(jù)訪問對象來調(diào)用模型服務(wù)類中的 getNews() 方法。(清單 1 中所示的一小段 Action 類代碼說明了如何從 Action 類中調(diào)用模型服務(wù)。)
getNews() 方法實現(xiàn)了所有必需的業(yè)務(wù)邏輯與數(shù)據(jù)檢索邏輯。一旦從數(shù)據(jù)源取出了相關(guān)數(shù)據(jù),模型服務(wù)就可以聚集數(shù)據(jù)訪問對象并將它發(fā)送回給 Action 類。
清單 1. Action 類的部分代碼
/*
* Create a pre-defined Data Access Object
*/
newsSearchResultVOB = new NewsSearchResult();
/*
* Call Business layer NewsMs´s searchNews method passing the NewsCOD
* and get back a Collection of News
*/
newsList = (Vector) newsMs.getNews(newsSearchResultVOB);
/*
* Put the NewsList in the request.
*
*/
request.setAttribute(SystemConstants.NEWS_LIST, newsList);
//Set the newslist in the form
newsFrm.setNewsDetails(newsList);
// Set the NewsForm in the request
request.setAttribute(mapping.getAttribute(), newsFrm);
//return actionForward;
return mapping.findForward("success");
從模型服務(wù)處接收到數(shù)據(jù)訪問對象后, Action 類通過傳遞轉(zhuǎn)給模型服務(wù)的同一個數(shù)據(jù)訪問對象來調(diào)用 WSManager 類中的 getStocks() 方法。 WSManager 類中的 getStocks() 方法執(zhí)行 JAX-RPC 來訂購股票 Web 服務(wù)。
從股票 Web 服務(wù)獲得的結(jié)果要轉(zhuǎn)變成本地的 schema 并且聚集回到預(yù)定義數(shù)據(jù)訪問對象中去。這個對象從它被調(diào)用的地方傳回到 action 類。 清單 2 闡明了如何使用 WSManager 中的 JAX-RPC 來訂購 Web 服務(wù),以及如何將股票 Web 服務(wù)的結(jié)果轉(zhuǎn)變成本地 schema 并聚集回到數(shù)據(jù)訪問對象中去。
清單 2. 訂購 Web 服務(wù)及傳送響應(yīng)
public Vector getStocks() throws Exception {
//Method level variables
Vector stockValues = new Vector();
try {
// create service factory
javax.xml.rpc.ServiceFactory factory =
javax.xml.rpc.ServiceFactory.newInstance();
// define targetNameSpace
String targetNamespace = "http://www.themindelectric.com/"
+ "wsdl/net.xmethods.services.stockquote.StockQuote/";
// define qname
QName serviceName =
new QName(targetNamespace,
"net.xmethods.services.stockquote.StockQuoteService");
// define portname
QName portName =
new QName(targetNamespace,
"net.xmethods.services.stockquote.StockQuotePort");
// define operation name
QName operationName = new QName("urn:xmethods-delayed-quotes",
"getQuote");
//Specify wsdl location
java.net.URL wsdlLocation =
new java.net.URL("http://services.xmethods.net/soap/urn:xmethods-
delayed-quotes.wsdl");
// create service
javax.xml.rpc.Service service =
factory.createService(wsdlLocation, serviceName);
// create call
javax.xml.rpc.Call call =
service.createCall(portName, operationName);
//Populate an array with list of stock names
Vector populatedStockNames = this.getPopulatedList();
//Loop through the populated list, and for each stock get a result
java.util.Iterator i = this.getPopulatedList().iterator();
String stockName = null;
Float result;
com.ddj.wsstruts.valueobject.StockValue stockValue = null;
while(i.hasNext()) {
stockName = (String) i.next();
// invoke the remote web service
result = (Float) call.invoke(new Object[] {stockName});
if(category.isDebugEnabled()) {
category.debug(" The quote for " + stockName + " is: " + result);
}
//Set stock name and stock values in stockValue bean
stockValue = new com.ddj.wsstruts.valueobject.StockValue();
stockValue.setStockName(stockName);
stockValue.setStockValue(result.toString());
//Add the stockValue bean to stockValues vector
stockValues.add(stockValue);
}
}
一旦 Action 類調(diào)用完模型服務(wù)和 WSManager, ActionForm 中的股票值及信息內(nèi)容域就會被填充??刂凭捅环祷氐?JSP 頁面,從 ActionForm 獲取所有必需的值并打印 UI.
結(jié)束語
在本文中,您看到了 Structs 架構(gòu)是如何與 Web 服務(wù)相集成的。您也了解了如何使用 Structs 組件來提供和預(yù)訂 Web 服務(wù)。本文附帶的簡單應(yīng)用程序代碼將幫助您深入理解所有這些是如何工作的。
使用此處所闡明的體系結(jié)構(gòu),您可以開發(fā)出這樣的企業(yè)應(yīng)用程序,它非常健壯,很容易維護,而且能很容易地與早期應(yīng)用程序集成在一起。我希望您將可以開發(fā)出更多的關(guān)于 Structs 和 Web 服務(wù)的項目,并看到這種體系結(jié)構(gòu)在您自己的項目中是多么的有用。