首先簡(jiǎn)要介紹一下REST。REST代表Representational State Transfer,它是World Wide Web所依賴(lài)的一套架構(gòu)原則。Roy Fielding在他的博士論文“Architectural Styles and the Design of Network-based Software Architectures”中首次提出了這個(gè)概念。在他的論文中,F(xiàn)ielding明確指出REST和World Wide Web的五個(gè)架構(gòu)原則:
- 可尋址性(Addressability)。REST中的所有東西都基于資源 的概念。資源與OOP中的對(duì)象或其他名詞不同,它是一種抽象,必須可以通過(guò) URI 尋址或訪問(wèn)。
接口一致性(Interface uniformity)。與SOAP或其他標(biāo)準(zhǔn)不同,REST 要求用來(lái)操縱資源的方法或動(dòng)詞不是任意的。這意味著RESTful服務(wù)的開(kāi)發(fā)人員只能使用HTTP支持的方法,比如GET、PUT、POST、DELETE等等。因此不需要使用 WSDL 等服務(wù)描述語(yǔ)言。 - 無(wú)狀態(tài)(Statelessness)。為了增強(qiáng)可伸縮性,服務(wù)器端不存儲(chǔ)客戶(hù)機(jī)的狀態(tài)信息。這使服務(wù)器不與特定的客戶(hù)機(jī)相綁定,負(fù)載平衡變得簡(jiǎn)單多了。這還讓服務(wù)器更容易監(jiān)視、更可靠。
- 具象(Representational)。客戶(hù)機(jī)總是與資源的某種具象交互,絕不會(huì)直接與資源本身交互。同一資源還可以有多個(gè)具象。理論上說(shuō),持有資源的具象的任何客戶(hù)機(jī)應(yīng)該有操縱底層資源的足夠信息。
- 連通性(Connectedness)。任何基于REST的系統(tǒng)都應(yīng)該預(yù)見(jiàn)到客戶(hù)機(jī)需要訪問(wèn)相關(guān)的資源,應(yīng)該在返回的資源具象中包含這些資源。例如,可以以超鏈接的形式包含特定RESTful服務(wù)的操作序列中的相關(guān)步驟,讓客戶(hù)機(jī)可以根據(jù)需要訪問(wèn)它們。
JAX-RS
Web上的REST以下站點(diǎn)當(dāng)前正在使用REST:
Atom Publishing Protocol。Atom是REST協(xié)議最正規(guī)的實(shí)現(xiàn)之一,廣泛用于博客發(fā)布領(lǐng)域。
Sun的Cloud API。這是Sun的RESTful API,用于管理和創(chuàng)建計(jì)算、連網(wǎng)和存儲(chǔ)元素等云資源。
Digg的API。Digg 是一個(gè)流行的社交網(wǎng)站,它使用RESTful API讓用戶(hù)和合作伙伴能夠以編程方式與站點(diǎn)和數(shù)據(jù)交互。
Netflix API。Netflix是一個(gè)DVD出租網(wǎng)站,它使用RESTful API提供對(duì)影片目錄的細(xì)粒度訪問(wèn),以及以編程方式調(diào)整用戶(hù)隊(duì)列和獲取影片推薦。
Flickr API。Flickr是一個(gè)照片上傳網(wǎng)站,用戶(hù)可以使用它提供的RESTful API上傳、更換和搜索照片和像冊(cè)。
為什么需要另一個(gè)Java標(biāo)準(zhǔn)?定義JAX-RS這個(gè)新規(guī)范是為了簡(jiǎn)化基于REST的Java開(kāi)發(fā)。它主要關(guān)注使用Java注釋和普通舊式Java對(duì)象(POJO)實(shí)現(xiàn) RESTful 服務(wù)。盡管總是可以使用servlet實(shí)現(xiàn)RESTful服務(wù),但是以這種方式實(shí)現(xiàn)業(yè)務(wù)邏輯需要太多HTTP GET請(qǐng)求。
JAX-RS隱藏所有 HTTP并把servlet綁定到Java類(lèi)中的各個(gè)方法。注釋還可以動(dòng)態(tài)地提取HTTP請(qǐng)求中的信息,以及把應(yīng)用程序生成的異常映射到HTTP響應(yīng)碼。由于這些原因,JAX-RS是一種實(shí)現(xiàn)RESTful Java Web服務(wù)的有效方法。
Apache Wink和REST
我已經(jīng)介紹了REST和JAX-RS,現(xiàn)在開(kāi)始討論Apache Wink。Apache Wink 1.0是一個(gè)從頭設(shè)計(jì)的完全兼容的JAX-RS 1.0規(guī)范實(shí)現(xiàn)。它很容易使用和應(yīng)用于生產(chǎn)環(huán)境,它提供的特性可以增強(qiáng)核心JAX-RS規(guī)范。
Apache Wink運(yùn)行時(shí)架構(gòu)是JAX-RS 1.0規(guī)范的簡(jiǎn)單實(shí)現(xiàn)。Apache Wink部署在Java Platform, Enterprise Edition (Java EE)環(huán)境中,由以下三個(gè)組件組成:
- Apache Wink RestServlet。RestServlet在Web應(yīng)用程序的Java EE web.xml描述符文件中配置。這個(gè)servlet作為所有HTTP Web服務(wù)請(qǐng)求的主入口點(diǎn),它把請(qǐng)求和響應(yīng)對(duì)象實(shí)例分派給請(qǐng)求處理器進(jìn)行進(jìn)一步處理。
- 請(qǐng)求處理器。RequestProcessor是核心Apache Wink引擎,它由Apache Wink RestServlet初始化。請(qǐng)求處理器使用請(qǐng)求 URI 尋找、匹配和調(diào)用相應(yīng)的資源類(lèi)和方法。在請(qǐng)求處理期間發(fā)生的任何異常都會(huì)導(dǎo)致RequestProcessor調(diào)用Error Handler Chain以處理異常。
- 資源。在REST中,代表Web服務(wù)的任何組件或?qū)ο蠖急环Q(chēng)為資源。資源允許通過(guò)它的許多具象之一獲取和操縱數(shù)據(jù)。實(shí)現(xiàn)資源的POJO被稱(chēng)為資源類(lèi)。資源類(lèi)進(jìn)一步實(shí)現(xiàn)資源方法,資源方法實(shí)際處理底層業(yè)務(wù)邏輯。
- 整個(gè)請(qǐng)求周期被稱(chēng)為Apache Wink邏輯流,見(jiàn) 圖 1。
圖 1. Apache Wink 邏輯流
Apache Wink不但幫助實(shí)現(xiàn)RESTful Web服務(wù),而且提供一個(gè)強(qiáng)大的客戶(hù)機(jī)庫(kù),可以使用它輕松地消費(fèi)RESTful服務(wù)。最后,Apache Wink附帶一組內(nèi)置的提供者,它們幫助開(kāi)發(fā)人員支持多種行業(yè)標(biāo)準(zhǔn)的數(shù)據(jù)格式:XML、Atom、RSS、JSON、CSV和HTML。
RESTful設(shè)計(jì)
現(xiàn)在該實(shí)踐一下了。為了保持趣味性,我們要在Apache Wink 1.0上設(shè)計(jì)、實(shí)現(xiàn)和部署一個(gè)不太簡(jiǎn)單的RESTful服務(wù)。這個(gè)服務(wù)是PayPal Payflow支付網(wǎng)關(guān)服務(wù)的RESTful包裝器,它可以通過(guò)Internet進(jìn)行信用卡處理。但是,對(duì)于這個(gè)示例,我們只關(guān)注它的交易查詢(xún)功能。只要提供一個(gè)屬于經(jīng)過(guò)身份驗(yàn)證的用戶(hù)的惟一ID,這個(gè)功能可以查詢(xún)?nèi)魏谓灰椎臓顟B(tài)。
資源/URI設(shè)計(jì)
首先定義服務(wù)的接口模型并給它分配URI,這使服務(wù)成為REST中的資源。因?yàn)檫@個(gè)服務(wù)的功能是提供交易狀態(tài),可以公開(kāi) 清單 1 所示的 URI。
清單 1. 交易服務(wù)的URI模式
以下是引用片段: /transactions /transactions{id} |
transactions URI表示系統(tǒng)中的所有交易。使用/transactions{id}查詢(xún)某一交易的狀態(tài)。{id}代表對(duì)應(yīng)于交易模型的交易ID的惟一字母數(shù)字值。另外,使用 清單2中的模式驗(yàn)證用戶(hù)的身份,其中的UNAME、VNAME、PNAME和PWD是在注冊(cè)時(shí)分配給商人的Payflow網(wǎng)關(guān)登錄憑證的組成部分。
清單 2. 在查詢(xún)字符串中包含用戶(hù)憑證的URI模式
以下是引用片段: /transactions{id}?user=UNAME&vendor=VNAME&partner=PNAME&pwd=PWD |
數(shù)據(jù)設(shè)計(jì)
每個(gè)RESTful接口必須決定支持它的客戶(hù)機(jī)使用哪種具象。Apache Wink 1.0可以支持XML、JSON、HTML和Atom 等,這個(gè)示例使用JSON,因?yàn)檫@是一種流行的格式,而且Ajax應(yīng)用程序很容易使用JavaScript代碼。清單 3 是一個(gè)采用JSON格式的交易狀態(tài)示例。
清單 3. JSON字符串形式的交易狀態(tài)響應(yīng)
以下是引用片段: { "RESULT":19,"PNREF":"V19A2A1A7CC5", "RESPMSG":"Original transaction ID not found: V19A2A192BE9", "AUTHCODE":null,"CVV2MATCH":null,"AVSADDR":null, "AVSZIP":null,"IAVS":null,"CARDSECURE":null } |
HTTP方法設(shè)計(jì)
最后,必須決定使用哪些HTTP方法操作資源及其功能。一定要堅(jiān)持這些HTTP方法的正規(guī)用法,不要偏離規(guī)范。例如,GET應(yīng)該是安全的只讀冪等(idempotent)調(diào)用,它不應(yīng)該以任何方式更改資源的狀態(tài)。違反這條原則會(huì)增加復(fù)雜性,給客戶(hù)機(jī)帶來(lái)混亂。在這個(gè)示例中,因?yàn)橄M麑?duì)單一交易的狀態(tài)執(zhí)行只讀查詢(xún),顯然應(yīng)該使用GET方法,使用的URI模式見(jiàn) 清單 4。
清單 4. 交易服務(wù)示例URI模式
以下是引用片段: /transactions{id} |
每個(gè)GET調(diào)用返回一個(gè)JSON格式的字符串,這是查詢(xún)的交易的狀態(tài)數(shù)據(jù),見(jiàn) 清單 5。
清單 5. 包含交易ID的GET請(qǐng)求
以下是引用片段: GET /transactions/V19A2A192BE9 HTTP/1.1 |
但是,這種查詢(xún)交易狀態(tài)的模型有一個(gè)問(wèn)題:服務(wù)無(wú)法驗(yàn)證查詢(xún)交易的用戶(hù)是否確實(shí)是交易的所有者。為了解決這個(gè)問(wèn)題,允許客戶(hù)機(jī)在URI中作為查詢(xún)參數(shù)傳遞登錄憑證,見(jiàn) 清單 6。
清單 6. 在查詢(xún)字符串中包含安全憑證的GET請(qǐng)求
以下是引用片段: GET /transactions/V19A2A192BE9?user=winktest&vendor=winktest &partner=PayPal&pwd=wink123 HTTP/1.1 |
Apache Wink服務(wù)實(shí)現(xiàn)
Apache Wink服務(wù)實(shí)現(xiàn)為POJO(即普通的Java類(lèi)),它使用JAX-RS注釋把HTTP請(qǐng)求映射到Java方法。在默認(rèn)情況下,服務(wù)可以是單實(shí)例的,也可以為每個(gè)請(qǐng)求創(chuàng)建服務(wù)實(shí)例。在這個(gè)示例中,通過(guò)創(chuàng)建TransactionResource類(lèi)實(shí)現(xiàn)Apache Wink RESTful服務(wù)。它解析狀態(tài)查詢(xún)請(qǐng)求,驗(yàn)證用戶(hù)憑證,調(diào)用Payflow網(wǎng)關(guān)服務(wù),然后以JSON對(duì)象的形式返回交易的狀態(tài)。清單 7 給出TransactionResource類(lèi)的代碼片段。
清單 7. Apache Wink服務(wù)Java類(lèi)片段
以下是引用片段: package org.openengine.wink.example.payflow; import ...; @Path("/transactions") public class TransactionResource { @GET @Path("{pnref}") @Produces(MediaType.APPLICATION_JSON) public JSONObject doInquiry(@PathParam("pnref") String pnref, @QueryParam("user") String userName, @QueryParam("vendor") String vendorName, @QueryParam("partner") String partnerName, @QueryParam("pwd") String password) { try { return getTxnStatus(pnref, userName, vendorName, partnerName, password); } catch (JSONException e) { throw new WebApplicationException (Response.Status.INTERNAL_SERVER_ERROR); } } .... |
@javax.ws.rs.Path注釋指出這個(gè)類(lèi)是JAX-RS服務(wù)。所有JAX-RS服務(wù)都需要這個(gè)注釋。@Path 注釋的值 /transactions 指出交易服務(wù)的URI的相對(duì)路徑。
@GET注釋指出這個(gè)方法本身映射到的HTTP動(dòng)詞。方法級(jí)@Path注釋的值相對(duì)于主URI指定子根。方法級(jí)@Path和@PathParam中的{pnref}表示交易的惟一ID。例如,如果URI是 /transactions/V19A2A192BE9,V19A2A192BE9字符串會(huì)被注入getTxnStatus方法的{pnref}參數(shù)中。
@javax.ws.rs.QueryParam 注釋與@PathParam相似,但是它把各個(gè)URI查詢(xún)參數(shù)注入Java參數(shù)中,見(jiàn) 清單 8。
清單 8. 包含查詢(xún)參數(shù)的URI
以下是引用片段: /transactions/V19A2A192BE9?user=winktest&vendor=winktest&partner=PayPal&pwd=wink123 |
Apache Wink服務(wù)配置
Apache Wink應(yīng)用程序通常打包為WAR文件,部署在Apache Tomcat等servlet容器中。與其他Web應(yīng)用程序一樣,Apache Wink服務(wù)也需要一個(gè)web.xml文件。清單 9 給出示例Apache Wink服務(wù)的web.xml文件的內(nèi)容。
清單 9. web.xml Web配置文件
以下是引用片段: <web-app> <display-name>Wink demo</display-name> <description>Demonstration of SDK features</description> <!-- Wink SDK servlet configuration. This servlet handles HTTP requests of SDK web service on application server.--> <servlet> <servlet-name>restSdkService</servlet-name> <servlet-class> org.apache.wink.server.internal.servlet.RestServlet </servlet-class> <init-param> <param-name>applicationConfigLocation</param-name> <param-value>/WEB-INF/application</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>restSdkService</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping> </web-app> |
可以看到web.xml文件中定義了Apache Wink RestServlet和它的url-pattern。還有RestServlet的初始化參數(shù),它指向/WEB-INF/application目錄中的一個(gè)文件。這個(gè)文件包含JAX-RS應(yīng)該部署的所有類(lèi)和對(duì)象的列表。您不需要這個(gè)配置文件,可以讓?xiě)?yīng)用程序類(lèi)以編程方式列出服務(wù)實(shí)現(xiàn)的資源,但是這個(gè)示例使用應(yīng)用程序配置文件的方式,見(jiàn) 清單10。
清單 10. 應(yīng)用程序配置文件
以下是引用片段: org.openengine.wink.example.payflow.TransactionResource |
運(yùn)行Apache Wink服務(wù)
要想運(yùn)行這個(gè)服務(wù),首先必須在Tomcat上構(gòu)建和部署應(yīng)用程序。執(zhí)行以下步驟:
把PayFlow項(xiàng)目的內(nèi)容解壓到C驅(qū)動(dòng)器上的一個(gè)文件夾。C:\PayFlow目錄應(yīng)該像 圖 2 這樣。
圖 2. PayFlow 示例項(xiàng)目的目錄結(jié)構(gòu)
把JAVA_HOME和ANT_HOME變量設(shè)置為自己的Java和Apache Ant安裝目錄。
在系統(tǒng)變量PATH中添加JAVA_HOME/bin 和 ANT_HOME/bin。
從C:\PayFlow目錄運(yùn)行Ant,從而對(duì)PayFlow項(xiàng)目執(zhí)行Ant構(gòu)建。
這個(gè)步驟應(yīng)該會(huì)構(gòu)建PayFlow項(xiàng)目并創(chuàng)建文件PayFlow.war,見(jiàn) 圖 3。
圖 3. Ant 腳本構(gòu)建的輸出
把PayFlow.war文件復(fù)制到TOMCAT主目錄下的webapps目錄中,見(jiàn) 圖 4。
圖 4. 在 Tomcat 服務(wù)器上部署 PayFlow 項(xiàng)目
通過(guò)運(yùn)行TOMCAT_HOME\bin文件夾中的startup.bat文件啟動(dòng)Tomcat Web服務(wù)器。
啟動(dòng)您喜歡的瀏覽器,訪問(wèn)URI http://localhost:8080/PayFlow/rest/transactions/V19A2A192BE9?
user=winktest&vendor=winktest&partner=PayPal&pwd=wink123>(假設(shè)Tomcat Web服務(wù)器配置為監(jiān)聽(tīng)端口8080)。
瀏覽器應(yīng)該會(huì)提示您保存文件 V19A2A192BE9,其中包含JSON格式的交易狀態(tài),見(jiàn) 圖 5。
圖 5. 瀏覽器保存文件窗口,保存包含交易狀態(tài)的JSON文件
結(jié)束語(yǔ)
本文簡(jiǎn)要介紹了REST架構(gòu)和JAX-RS Java標(biāo)準(zhǔn)(這個(gè)標(biāo)準(zhǔn)用于簡(jiǎn)化RESTful服務(wù)實(shí)現(xiàn))的基本知識(shí)。然后討論了新的Apache Wink 1.0框架,這是一個(gè)完全兼容的容易使用的JAX-RS 1.0規(guī)范實(shí)現(xiàn)。介紹了Apache Wink的底層架構(gòu),然后設(shè)計(jì)和實(shí)現(xiàn)了一個(gè)新的示例Apache Wink Web服務(wù),討論了部署和運(yùn)行它所需的步驟。