2004 年 9 月 01 日 學(xué)習(xí)如何利用 servlet 端點(diǎn)模型來擴(kuò)展無狀態(tài) JAX-RPC Web 服務(wù),并使用 HTTP 會(huì)話來構(gòu)建有狀態(tài) Web 服務(wù)應(yīng)用程序。我們用一個(gè)簡單的購物車 Web 服務(wù)范例來加以說明。 Web 服務(wù)開發(fā)組對(duì)于 Web 服務(wù)本身是否為無狀態(tài)存在著爭論。一些開發(fā)人員提出了類似于 Web 服務(wù)資源框架(WS-Resource Framework,請(qǐng)參閱 參考資料)那樣的規(guī)范來定義有狀態(tài) Web 服務(wù),而當(dāng)前廣泛使用的 JAX-RPC 規(guī)范主要處理無狀態(tài)服務(wù)。 無狀態(tài)的意思是,對(duì) Web 服務(wù)的調(diào)用與之前的互操作沒有相關(guān)性,并且不為以后的互操作而保存當(dāng)前活動(dòng)的信息。 您可以通過一些實(shí)際情況來了解無狀態(tài)的含義,比如像 JavaBean 和無狀態(tài)會(huì)話企業(yè) JavaBean 組件之類的 JAX-RPC 服務(wù)端點(diǎn)組件并不保持狀態(tài)信息。然而,您仍然可以利用基礎(chǔ)傳輸協(xié)議固有的會(huì)話支持,并創(chuàng)建有狀態(tài) Web 服務(wù),這取決于服務(wù)端點(diǎn)部署于何處。本文說明了當(dāng)服務(wù)端點(diǎn)部署到 servlet 容器時(shí),如何在 JAX-RPC 應(yīng)用程序中使用 HTTP 的會(huì)話功能。我們將在一個(gè)簡單的購物車 Web 服務(wù)中對(duì)此進(jìn)行說明。 范例:購物車服務(wù) 清單 1定義了一個(gè)簡單的購物車 Web 服務(wù),它允許顧客在購物車中添加、刪除和查詢商品條目。一個(gè)需求是該購物車需要在整個(gè)購物期間保持已選擇的商品,直到顧客付款后離開。本文的其余部分說明為什么這個(gè)簡單的有狀態(tài)服務(wù)可以使用多種 JAX-RPC 技術(shù)來實(shí)現(xiàn)。 清單 1. 購物車接口 package shopping; public interface Cart extends java.rmi.Remote { public void addItem(String item) throws java.rmi.RemoteException; public void removeItem(String item) throws java.rmi.RemoteException; public int getItemNumber() throws java.rmi.RemoteException; public void checkout() throws java.rmi.RemoteException, shopping.NoUserSessionFault; } |
服務(wù)的實(shí)現(xiàn) 該服務(wù)通過兩個(gè)步驟實(shí)現(xiàn): - 實(shí)現(xiàn)
ServiceLifecycle 接口。 - 處理會(huì)話數(shù)據(jù)。
實(shí)現(xiàn) ServiceLifecyle 接口 JAX-RPC 定義了一個(gè) javax.xml.rpc.server.ServiceLifecyle 接口。如果服務(wù)實(shí)現(xiàn)類實(shí)現(xiàn)這個(gè)接口,就需要 JAX-RPC 運(yùn)行時(shí)系統(tǒng)來管理相應(yīng)的服務(wù)端點(diǎn)實(shí)例的生存周期。該 ServiceLifecyle 接口有兩個(gè)方法: init 和 destroy 。 init 方法使用泛型類型 java.lang.Object 的“context”參數(shù),但如果服務(wù)端點(diǎn)部署到基于 servlet-容器的 JAX-RPC 運(yùn)行時(shí)系統(tǒng),那么任何適應(yīng)的 JAX-RPC 運(yùn)行時(shí)系統(tǒng)都需要傳遞一個(gè) javax.xml.rpc.server.ServletEndpointContext 實(shí)例。 在我們的范例中,服務(wù)實(shí)現(xiàn)類 CartSoapBindingImpl (參見 清單 2)實(shí)現(xiàn)了這樣一個(gè) ServiceLifecyle 接口。在實(shí)現(xiàn)實(shí)例進(jìn)行了實(shí)例化之后,JAX-RPC 運(yùn)行時(shí)調(diào)用它的 init 方法將 context 參數(shù)傳給類型 javax.xml.rpc.server.ServletEndpointContext 。這是可以做到的,因?yàn)橘徫镘嚪?wù)是基于 servlet 的。然后 context 對(duì)象保存為私有字段,這樣后來的調(diào)用可以使用它來獲取 HttpSession 。當(dāng)清除時(shí),一旦調(diào)用 destroy 方法,context 對(duì)象就被設(shè)置為 null。 清單 2. 實(shí)現(xiàn) ServiceLifecycle 接口 public class CartSoapBindingImpl implements shopping.Cart, ServiceLifecycle { private ServletEndpointContext jaxrpcContext; public void init(Object context) throws ServiceException { jaxrpcContext = (ServletEndpointContext) context; } public void destroy() { jaxrpcContext = null; } ... } | 處理會(huì)話數(shù)據(jù) javax.xml.rpc.server.ServletEndpointContext 接口包含幾個(gè)方法。其中與本文主題有關(guān)的一個(gè)方法是 getHttpSession() ,它返回一個(gè) javax.servlet.http.HttpSession 實(shí)例。對(duì)于每個(gè)購物車服務(wù)的方法,該方法首先針對(duì)以前緩存的 ServletEndpointContext 調(diào)用 getHttpSession 方法,以獲得 HttpSession 對(duì)象。一旦 HttpSession 可用,服務(wù)實(shí)現(xiàn)就能夠從 HttpSession 對(duì)象中檢索用戶會(huì)話數(shù)據(jù),正如常規(guī) servlet 一樣(參見 清單 3)。請(qǐng)注意, 清單 3 中定義的 SessionData 類是用于保存各種購物車信息的用戶自定義數(shù)據(jù)結(jié)構(gòu)(點(diǎn)擊本文頂部或底部的 code 圖標(biāo))。 清單 3. 從 ServletEndpointContext 中檢索 HTTPSession public class CartSoapBindingImpl implements shopping.Cart, ServiceLifecycle { private ServletEndpointContext jaxrpcContext; ... public void addItem(java.lang.String item) throws java.rmi.RemoteException { getSessionData().addItem(item); } // SessionData is a utility class to store all the shopping cart related information private SessionData getSessionData() { SessionData sd = (SessionData) jaxrpcContext.getHttpSession().getAttribute(SessionData.SESSION_KEY); if(sd == null) { sd = new SessionData(); jaxrpcContext.getHttpSession().setAttribute(SessionData.SESSION_KEY, sd); } return sd; } ... } |
客戶端的啟用 與自動(dòng)處理會(huì)話的典型 Web 瀏覽器不同的是,JAX-RPC 客戶端并不缺省的使用目標(biāo)服務(wù)端點(diǎn)參與會(huì)話。因?yàn)檫@一點(diǎn),參與會(huì)話的客戶端必須明確的將一個(gè)專門的存根屬性( javax.xml.rpc.Stub.SESSION_MAINTAIN_PROPERTY 或者 " javax.xml.rpc.session.maintain ")設(shè)置為 true,這樣 JAX-RPC 客戶端運(yùn)行時(shí)將維護(hù)調(diào)用客戶端的會(huì)話(參見 清單 4)。否則,服務(wù)端點(diǎn)獲取的 HttpSession 對(duì)象將為空,或者各個(gè)服務(wù)調(diào)用的 HttpSession 很可能不一致。 請(qǐng)注意 JAX-RPC 客戶端的定義是相對(duì)于服務(wù)的,這些服務(wù)與客戶端進(jìn)行通信,并且不同服務(wù)的存根被認(rèn)為是不同的客戶端。同樣,即使存根可能與運(yùn)行在相同后端服務(wù)器的服務(wù)進(jìn)行對(duì)話,典型的 JAX-RPC 運(yùn)行時(shí)也分別為每個(gè)單獨(dú)的存根維護(hù)會(huì)話數(shù)據(jù)。因此,如果您編寫應(yīng)用程序來調(diào)用兩個(gè)不同的服務(wù),就不應(yīng)該期望能夠跨這兩個(gè)服務(wù)調(diào)用來對(duì)會(huì)話進(jìn)行維護(hù)。 清單 4. 要求 JAX-RPC 運(yùn)行時(shí)維護(hù)會(huì)話 javax.xml.rpc.Stub jaxrpcStub = (javax.xml.rpc.Stub) getVendorSpecificStub(); jaxrpcStub._setProperty(javax.xml.rpc.Stub.SESSION_MAINTAIN_PROPERTY, Boolean.TRUE); |
結(jié)束語 即使基于 JAX-RPC 的 Web 服務(wù)本身是無狀態(tài)的,您仍然可以使用 servlet 容器提供的會(huì)話支持并采用特定的服務(wù)端點(diǎn)模型(比如基于 servlet 的端點(diǎn))來構(gòu)建有狀態(tài) Web 服務(wù)應(yīng)用程序。同時(shí),客戶端也需要切換一個(gè)特定的標(biāo)志來通知 JAX-RPC 運(yùn)行時(shí)系統(tǒng)對(duì)客戶端會(huì)話進(jìn)行維護(hù)。 |