使用REST協議來實現SOA服務<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
REST是Representation State Transfer(表示層有狀態的傳輸協議)的簡稱,它是一種Web架構類型,由Roy Fielding於2000年在他的博士論文中提出來的。REST的基本思想是如何充分利用HTTP協議的功能,它特別關注以下內容:
(1) REST關注資源,即,每種服務都應該設計成對資源進行某種操作;
(2) REST充分利用了HTTP協議中的謂詞的功能,它不僅使用我們常用的GET和POST謂詞,而且還使用PUT和DELETE謂詞
在我們前面使用POX-over-HTTP方法完成的的SOA服務中,我們就使用了POST這個HTTP謂詞。雖然前面我們僅僅列出了幾個服務的源代碼,它們都可以使用POST謂詞來實現,當然,您也可以使用其它謂詞實現這些服務。REST的思想就是,我們爲什麼不充分利用HTTP協議謂詞的功能,把常見的CRUD操作映射到HTTP協議的謂詞上,這樣,消息交換協議就顯得更加清晰。因此,我們可以把CRUD操作和相應的HTTP謂詞進行關聯,具體關係如下表所示:
HTTP謂詞 |
CRUD操作 |
操作表述 |
POST |
CREATE |
Save new resources |
GET |
READ |
Read resources |
PUT |
UPDATE |
Modify existing resources |
DELETE |
DELETE |
Delete resources |
請您記住,資源及其對應的值代表了系統的狀態,系統狀態的轉換將遵行以下幾個規則:
(1) POST、PUT和DELETE謂詞可以改變資源的狀態
(2) GET謂詞不能修改資源的狀態
(3) 往系統增加新資源時應該使用POST謂詞
(4) 修改系統中的資源應使用PUT謂詞
(5) 從系統中刪除資源應調用DELETE謂詞
(6) 系統交換協議應該是無狀態的,即本次方法的調用不應依賴於上次的方法調用
這些聽起來很有趣,但資源究竟是什麼?資源其實就是HTTP四個謂詞能覆蓋的請求操作的範圍。拿我們前面的討論過的商品對象來說,它就是一個典型的資源實例。您可以看到,在我們的實現代碼中(見代碼清單5),有一個IF-THEN的判斷語句塊,這個語句塊根據請求中的含有的方法名來執行不同的操作,實際上,我們可以通過使用HTTP謂詞,避免這些條件判斷。那麼,這是如何實現的呢?
如果客戶端想新添一個商品,它需要調用insertItem服務,先要爲這個新商品創建一個XML文檔,然後通過POST方法把這個XML文檔發送到服務器端,服務器的應答可以是一個比較通用的一個結果對象。
代碼清單9— 通用結果對象Outcome的源代碼
@XmlRootElement(name="Outcome")
public class Outcome {
private String retCode;
private String retMessaget;
...
}
服務器端的實現代碼也會因此而減少,因爲現在我們不需要考慮條件判斷語句(請把下面的代碼和代碼清單5相比較):
代碼清單10— REST風格的新建服務的服務器端實現代碼
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
try{
JAXBContext jaxbContext = JAXBContext.newInstance(Item.class, Outcome.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
//Receiving the XML request and transform it into a Java object
Item item = (Item) unmarshaller.unmarshal(request.getInputStream());
System.out.println("Inserting item# "+item.getId());
// ... insert item
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,Boolean.TRUE);
Outcome outcome = new Outcome();
outcome.setRetCode("OK");
outcome.setRetMessaget("Item was inserted successfully");
marshaller.marshal(outcome, response.getOutputStream());
}catch (Exception e) {
throw new ServletException(e);
}}
請注意,上面的代碼只是REST協議的一個非常簡單的手工實現,我們是使用一個簡單的Servlet實現的,因爲我們想把注意力集中在消息交換協議的一些重要概念上。雖然REST可由多種方法實現,比如JAX-WS(我們在討論SOAP時會講到)或Axis2都可實現REST。
好,現在回到我們的例子,updateItem服務可由與上面類似的代碼實現,唯一的區別就是把update服務的實現代碼放在Servlet的doPut方法中,而不是放在doPost方法中。其實,REST方法還有一點與衆不同,我們這裏提前說一下。
代碼清單11— REST風格的新建服務的客戶器端實現代碼
Item item = new Item();
item.set...
//Prepare and establish the connection with the service
URL url = new URL("http://localhost/SoaBookREST/itemService");
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setDoOutput(true);
//Set the HTTP request method
con.setRequestMethod("POST");
con.connect();
JAXBContext jaxbContext = JAXBContext.newInstance(Item.class, Outcome.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,Boolean.TRUE);
//Send the XML request to the service
marshaller.marshal(item, con.getOutputStream());
//Get the XML response from the service and deserialize it
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Outcome outcome = (Outcome)
unmarshaller.unmarshal(con.getInputStream());
至於deleteItem、findItemById和findAllItems服務,REST方法使用的消息交換協議會有些不同,這些操作實際上不需要上傳XML到服務器,它們最多只需要傳給服務器一個Id號,像findAllItems根本就不要傳給服務器任何輸入。在這些情況下,URI和HTTP謂詞就能提供服務器需要的所有信息。下表列出了REST請求的一些URI實例:
謂詞 |
URI示例 |
操作 |
DELETE |
http://localhost/SoaBookREST/itemService/14 |
刪除第14號商品 |
GET |
http://localhost/SoaBookREST/itemService/14 |
獲取第14號商品 |
GET |
http://localhost/SoaBookREST/itemService |
獲取所有商品 |
您會從上表看到,HTTP請求由謂詞加URI構成,它清楚地告訴服務器它要求哪個服務。請您注意,上面的URI包含了一些額外數據(比如Id號),因此您需要web描述文件中配置相應的URL映射模式,而不是針對某個具體的URL進行配置,web.xml的示例配置如下:
代碼清單12— web.xml中servlet的映射配置
<servlet-mapping>
<servlet-name>ItemService</servlet-name>
<url-pattern>/itemService/*</url-pattern>
</servlet-mapping>
下面是最後兩個操作的服務實現代碼:
代碼清單13— REST風格讀取服務的實現
protected void doGet(HttpServletRequest request,HttpServletResponse response)
throws ServletException, IOException
{
try{
if (request.getPathInfo()==null){
//findAllItems
ItemList itemList = new ItemList();
itemList.setList(new ArrayList());
//retrieve all items
...
itemList.getList().add(...);
...
//Send the XML message to the client
JAXBContext jaxbContext = JAXBContext.newInstance(ItemList.class, Item.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,Boolean.TRUE);
marshaller.marshal(itemList, response.getOutputStream());
} else {
//findItemById
int id = (new Integer(request.getPathInfo().substring(1))).intValue();
//retrieve item by id (e.g. from a database)
Item item = ...
JAXBContext jaxbContext = JAXBContext.newInstance(Item.class);
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,Boolean.TRUE);
marshaller.marshal(item, response.getOutputStream());
}
}catch (Exception e) {
throw new ServletException(e);
}}
如果您想更新一個商品,您需要調用如下的REST風格的URI,其中包含了商品的Id信息。
PUT http://localhost/SoaBookREST/itemService/14
其實,REST的這種調用哲學和HTTP請求的自解釋性的特點是一致的,上面的URI可以讀作“更新14號商品”。
一般說來,如果您要使用上面的方法創建非CRUD服務,您有以下兩種選擇:
(1) 使用合適的謂詞,新建一個Servelet
(2) 重用已有的Servlet和謂詞,但需要添加新的代碼,以解析和組裝HTTP請求中的業務邏輯。
您也可以考慮在請求中加入參數這種比較實用的辦法,請求中添加的參數可以讓執行的業務流程發生變化,雖然REST的追隨者不喜歡這樣做。這種方法的請求格式可能如下所示:
http://localhost/SoaBookREST/itemService?id=14
REST追隨者們對上面的辦法可能頗有微詞,實際上,上面的這種請求格式依賴於具體的參數名(id),這多少爲REST這種簡單線性的請求風格增添了複雜性。