使用 Struts 2 開發 RESTful 服務
級別: 初級
李 剛, 自由撰稿人
2009 年 8 月 28 日
從 V2.1 開始,Struts 2 開始提供 Convention 插件,它允許根據“約定”來搜索 Action,以及管理 Action 和 Result 的映射。另外,Struts 2.1 還提供了 REST 插件,使 Struts 2 可以支持 Rails 風格的 URL,以對外提供 REST 風格的資源服務。本文作者通過代碼示例演示了這些特性。
REST 是英文 Representational State Transfer 的縮寫,這個術語由 Roy Thomas Fielding 博士在他的論文《Architectural Styles and the Design of Network-based Software Architectures》中提出。從這篇論文的標題可以看出:REST 是一種基于網絡的軟件架構風格。
提示:國內很多網絡資料將 REST 翻譯為“表述性狀態轉移”,不過筆者對這個翻譯不太認同。因為這個專業術語無法傳達 REST 的含義,讀者可以先不要理會 REST 到底該如何翻譯,盡量先去理解 REST 是什么?有什么用?然后再來看這個術語的翻譯。關于 Roy Thomas Fielding 博士的原文參見如下地址:http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm。
REST 架構是針對傳統 Web 應用提出的一種改進,是一種新型的分布式軟件設計架構。對于異構系統如何進行整合的問題,目前主流做法都集中在使用 SOAP、WSDL 和 WS-* 規范的 Web Services。而 REST 架構實際上也是解決異構系統整合問題的一種新思路。
如果開發者在開發過程中能堅持 REST 原則,將可以得到一個使用了優質 Web 架構的系統,從而為系統提供更好的可伸縮性,并降低開發難度。關于 REST 架構的主要原則如下:
- 網絡上的所有事物都可被抽象為資源(Resource)。
- 每個資源都有一個唯一的資源標識符(Resource Identifier)。
- 同一資源具有多種表現形式。
- 使用標準方法操作資源。
- 通過緩存來提高性能。
- 對資源的各種操作不會改變資源標識符。
- 所有的操作都是無狀態的(Stateless)。
僅從上面幾條原則來看 REST 架構,其實依然比較難以理解,下面筆者將從如下二個方面來介紹 REST。
![]() ![]() |
![]()
|
現在的 Web 應用上包含了大量信息,但這些信息都被隱藏在 HTML、CSS 和 JavaScript 代碼中,對于普通瀏覽者而言,他們進入這個系統時無法知道該系統包含哪些頁面;對于一個需要訪問該系統資源的第三方系統而言,同樣無法明白這個系統包含多少功能和信息。
![]() |
|
從 REST 架構的角度來看,該系統里包含的所有功能和信息,都可被稱為資源(Resource),REST 架構中的資源包含靜態頁面、JSP 和 Servlet 等,該應用暴露在網絡上的所有功能和信息都可被稱為資源。
除此之外,REST 架構規范了應用資源的命名方式,REST 規定對應用資源使用統一的命名方式:REST 系統中的資源必須統一命名和規劃,REST 系統由使用 URI(Uniform Resource Identifier,即統一資源標識符)命名的資源組成。由于 REST 對資源使用了基于 URI 的統一命名,因此這些信息就自然地暴露出來了,從而避免 “信息地窖”的不良后果。
對于當今最常見的網絡應用來說,資源標識符就是 URI,資源的使用者則根據 URI 來操作應用資源。當 URI 發生改變時,表明客戶機所使用的資源發生了改變。
從資源的角度來看,當客戶機操作不同的資源時,資源所在的 Web 頁(將 Web 頁當成虛擬的狀態機來看)的狀態就會發生改變、遷移(Transfer),這就是 REST 術語中 ST(State Tranfer)的由來了。
客戶機為了操作不同狀態的資源,則需要發送一些 Representational 的數據,這些數據包含必要的交互數據,以及描述這些數據的元數據。這就是 REST 術語中 RE(Representational)的由來了。理解了這個層次之后,至于 REST 如何翻譯、或是否真正給它一個中文術語,讀者可自行決定。
![]() ![]() |
![]()
|
對于 REST 架構的服務器端而言,它提供的是資源,但同一資源具有多種表現形式(可通過在 HTTP Content-type 頭中包含關于數據類型的元數據)。如果客戶程序完全支持 HTTP 應用協議,并能正確處理 REST 架構的標準數據格式,那么它就可以與世界上任意一個 REST 風格的用戶交互。這種情況不僅適用于從服務器端到客戶端的數據,反之亦然——倘若從客戶端傳來的數據符合 REST 架構的標準數據格式,那么服務器端也可以正確處理數據,而不去關心客戶端的類型。
典型情況下,REST 風格的資源能以 XHTML、XML 和 JSON 三種形式存在,其中 XML 格式的數據是 WebServices 技術的數據交換格式,而 JSON 則是另一種輕量級的數據交換格式;至于 XHTML 格式則主要由瀏覽器負責呈現。當服務器為所有資源提供多種表現形式之后,這些資源不僅可以被標準 Web 瀏覽器所使用,還可以由 JavaScript 通過 Ajax 技術調用,或者以 RPC(Remote Procedure Call)風格調用,從而變成 REST 風格的 WebServices。
REST 架構除了規定服務器提供資源的方式之外,還推薦客戶端使用 HTTP 作為 Generic Connector Interface(也就是通用連接器接口),而 HTTP 則把對一個 URI 的操作限制在了 4 個之內:GET、POST、PUT 和 DELETE。通過使用通用連接器接口對資源進行操作的好處是保證系統提供的服務都是高度解耦的,從而簡化了系統開發,改善了系統的交互性和可重用性。
REST 架構要求客戶端的所有的操作在本質上是無狀態的,即從客戶到服務器的每個 Request 都必須包含理解該 Request 的所有必需信息。這種無狀態性的規范提供了如下幾點好處:
- 無狀態性使得客戶端和服務器端不必保存對方的詳細信息,服務器只需要處理當前 Request,而不必了解前面 Request 的歷史。
- 無狀態性減少了服務器從局部錯誤中恢復的任務量,可以非常方便地實現 Fail Over 技術,從而很容易地將服務器組件部署在集群內。
- 無狀態性使得服務器端不必在多個 Request 中保存狀態,從而可以更容易地釋放資源。
- 無狀態性無需服務組件保存 Request 狀態,因此可讓服務器充分利用 Pool 技術來提高穩定性和性能。
當然,無狀態性會使得服務器不再保存 Request 的狀態數據,因此需要在一系列 Request 中發送重復數據,從而提高了系統的通信成本。為了改善無狀態性帶來的性能下降,REST 架構填加了緩存約束。緩存約束允許隱式或顯式地標記一個 Response 中的數據,這樣就賦予了客戶端緩存 Response 數據的功能,這樣就可以為以后的 Request 共用緩存的數據,部分或全部的消除一些交互,增加了網絡的效率。但是用于客戶端緩存了信息,也就同時增加了客戶端與服務器數據不一致的可能,從而降低了可靠性。
![]() ![]() |
![]()
|
![]() |
|
從 Struts 2.1 開始,Struts 2 改為使用 Convention 插件來支持零配置。Convention 插件徹底地拋棄了配置信息,不僅不需要使用 struts.xml 文件進行配置,甚至不需要使用 Annotation 進行配置。而是由 Struts 2 根據約定來自動配置。
Convention 這個單詞的翻譯過來就是“約定”的意思。有 Ruby On Rails 開發經驗的讀者知道 Rails 有一條重要原則:約定優于配置。Rails 開發者只需要按約定開發 ActiveRecord、ActiveController 即可,無需進行配置。很明顯,Struts 2 的 Convention 插件借鑒了 Rails 的創意,甚至連插件的名稱都借鑒了“約定優于配置”原則。
由于 Struts 2 的 Convention 插件的主要特點是“約定優于配置”,當我們已經習慣了 Struts 2 的基本開發方法之后,如果希望改為使用 Convention 插件也非常容易,我們只要放棄 Stuts 2.1 應用原有的配置文件,改為按 Convention 插件的約定來定義 Action 即可。
以 Convention 插件為基礎,Struts 2.1 又新增了 REST 插件,允許 Struts 2 應用對外提供 REST 服務。REST 插件也無需使用 XML 進行配置管理。Struts 2.1 通過 REST 插件完全可以提供讓人和機器客戶端共同使用的資源,并支持 Ruby On Rails 風格的 URL。
![]() ![]() |
![]()
|
從本質上來看,Struts 2 依然是一個 MVC 框架,最初設計 Struts 2 時并沒有按 REST 架構進行設計,因此 Struts 2 本質上并不是一個 REST 框架。由于 Struts 2 提供了良好的可擴展性,因此允許通過 REST 插件將其擴展成支持 REST 的框架。REST 插件的核心是 RestActionMapper,它負責將 Rails 風格的 URL 轉換為傳統請求的 URL。
用 WinRAR 打開 struts2-rest-plugin-2.1.6 文件,看到該文件里包含一個 struts-plugin.xml 文件,該文件中包含如下一行:
<!-- 定義支持 REST 的 ActionMapper --> <bean type="org.apache.struts2.dispatcher.mapper.ActionMapper" name="rest" class="org.apache.struts2.rest.RestActionMapper" /> |
通過查看 RestActionMapper 的 API 說明,我們發現它可接受如下幾個參數:
- struts.mapper.idParameterName:用于設置 ID 請求參數的參數名,該屬性值默認是 id。
- struts.mapper.indexMethodName:設置不帶 id 請求參數的 GET 請求調用 Action 的哪個方法。該屬性值默認是 index。
- struts.mapper.getMethodName:設置帶 id 請求參數的 GET 請求調用 Action 的哪個方法。該屬性值默認是 show。
- struts.mapper.postMethodName:設置不帶 id 請求參數的 POST 請求調用 Action 的哪個方法。該屬性值默認是 create。
- struts.mapper.putMethodName:設置帶 id 請求參數的 PUT 請求調用 Action 的哪個方法。該屬性值默認是 update。
- struts.mapper.deleteMethodName:設置帶 id 請求參數的 DELETE 請求調用 Action 的哪個方法。該屬性值默認是 destroy。
- struts.mapper.editMethodName:設置帶 id 請求參數、且指定操作 edit 資源的 GET 請求調用 Action 的哪個方法。該屬性值默認是 edit。
- struts.mapper.newMethodName:設置不帶 id 請求參數、且指定操作 edit 資源的 GET 請求調用 Action 的哪個方法。該屬性值默認是 editNew。
在 RestActionMapper 的方法列表中,我們看到 setIdParameterName、setIndexMethodName、setGetMethodName、setPostMethodName、setPutMethodName、setDeleteMethodName、setEditMethodName、setNewMethodName 等方法,這些方法對應為上面列出的方法提供 setter 支持。
通常情況下,我們沒有必要改變 RestActionMapper 的參數,直接使用這些參數的默認值就可支持 Rails 風格的 REST。根據前面介紹可以看出:支持 REST 風格的 Action 至少包含如下 7 個方法:
- index:處理不帶 id 請求參數的 GET 請求。
- show:處理帶 id 請求參數的 GET 請求。
- create:處理不帶 id 請求參數的 POST 請求。
- update:處理帶 id 請求參數的 PUT 請求。
- destroy:處理帶 id 請求參數的 DELETE 請求。
- edit:處理帶 id 請求參數,且指定操作 edit 資源的 GET 請求。
- editNew:處理不帶 id 請求參數,且指定操作 edit 資源的 GET 請求。
如果請求需要向服務器發送 id 請求參數,直接將請求參數的值附加在 URL 中即可。表 1 顯示了 RestActionMapper 對不同 HTTP 請求的處理結果。
表 1. RestActionMapper 對 HTTP 請求的處理
HTTP 方法 | URI | 調用 Action 的方法 | 請求參數 |
GET | /book | index | |
POST | /book | create | |
PUT | /book/2 | update | id=2 |
DELETE | /book/2 | destroy | id=2 |
GET | /book/2 | show | id=2 |
GET | /book/2/edit | edit | id=2 |
GET | /book/new | editNew |
不幸地是,標準 HTML 語言目前根本不支持 PUT 和 DELETE 兩個操作,為了彌補這種不足,REST 插件允許開發者提交請求時額外增加一個 _method 請求參數,該參數值可以為 PUT 或 DELETE,用于模擬 HTTP 協議的 PUT 和 DELETE 操作。
![]() ![]() |
![]()
|
安裝 REST 插件非常簡單,只需按如下步驟進行即可:
- 將 Struts 2 項目下 struts2-convention-plugin-2.1.6.jar、struts2-rest-plugin-2.1.6.jar 兩個 JAR 包復制到 Web 應用的 WEB-INF\lib 路徑下。
- 由于 Struts 2 的 REST 插件還需要將提供 XML、JSON 格式的數據,因此還需要將 xstream-1.2.2.jar、json-lib-2.1.jar、ezmorph-1.0.3.jar 以及 Jakarta-Common 相關 JAR 包復制到 Web 應用的 WEB-INF/lib 路徑下。
- 通過 struts.xml、struts.properties 或 web.xml 改變 struts.convention.default.parent.package 常量的值,讓支持 REST 風格的 Action 所在的包默認繼承 rest-default,而不是繼承默認的 convention-default 父包。
對于第三個步驟而言,開發者完全可以不設置該常量,如果開發者不設置該常量,則意味著開發者必須通過 Annotation 為每個 Action 類設置父包。
![]() ![]() |
![]()
|
在實現支持 REST 的 Action 之前,我們先為系統提供一個 Model 類:Book,該 Book 類非常簡單,代碼如下:
public class Book { private Integer id; private String name; private double price; // 無參數的構造器 public Book(){} //id 屬性的 setter 和 getter 方法 public void setId(Integer id) { this.id = id; } public Integer getId() { return this.id; } // 省略 name 和 price 的 setter 和 getter 方法 ... } |
除了提供上面的 Book 類之外,我們還為該 Book 類提供一個業務邏輯組件:BookService。為了簡單起見,BookService 類不再依賴 DAO 組件訪問數據庫,而是直接操作內存中的 Book 數組——簡單地說,本系統中狀態是瞬態的,沒有持久化保存,應用運行過程中這些狀態一直存在,但一旦重啟該應用,則系統狀態丟失。下面是 BookService 類的代碼:
public class BookService { private static Map<Integer , Book> books = new HashMap<Integer , Book>(); // 保留下本圖書的 ID private static int nextId = 5; // 以內存中的數據模擬數據庫的持久存儲 static { books.put(1 , new Book(1 , "瘋狂 Java 講義" , 99)); books.put(2 , new Book(2 , "輕量級 Java EE 企業應用實戰" , 89)); books.put(3 , new Book(3 , "瘋狂 Ajax 講義", 78)); books.put(4 , new Book(4 , "Struts 2 權威指南" , 79)); } // 根據 ID 獲取 public Book get(int id) { return books.get(id); } // 獲取系統中全部圖書 public List<Book> getAll() { return new ArrayList<Book>(books.values()); } // 更新已有的圖書或保存新圖書 public void saveOrUpdate(Book book) { // 如果試圖保存的圖書的 ID 為 null,表明是保存新的圖書 if (book.getId() == null) { // 為新的圖書分配 ID。 book.setId(nextId++); } // 將保存 book books.put(book.getId() , book); } // 刪除圖書 public void remove(int id) { books.remove(id); } } |
從上面粗體字代碼可以看出,BookService 提供了 4 個方法,用于實現對 Book 對象的 CRUD 操作。
下面開始定義支持 REST 的 Action 類了,這個 Action 類與前面介紹 Struts 2 的普通 Action 存在一些差異——因為該 Action 不再用 execute() 方法來處理用戶請求,而是使用前面介紹的 7 個標準方法來處理用戶請求。除此之外,該 Action 總是需要處理 id 請求參數,因此必須提供 id 請求參數,并為之提供對應的 setter 和 getter 方法。
因為本系統已經提供了 Book Model 類,并且為了更好的模擬 Rails 中 ActiveController(Controller)直接訪問 ActiveRecord(Model)的方式,本系統采用了 ModelDriven 的開發方式,下面是本系統中支持 REST 的 Action 類的代碼。
// 定義返回 success 時重定向到 book Action @Results(@Result(name="success" , type="redirectAction" , params = {"actionName" , "book"})) public class BookController extends ActionSupport implements ModelDriven<Object> { // 封裝 id 請求參數的屬性 private int id; private Book model = new Book(); private List<Book> list; // 定義業務邏輯組件 private BookService bookService = new BookService(); // 獲取 id 請求參數的方法 public void setId(int id) { this.id = id; // 取得方法時順帶初始化 model 對象 if (id > 0) { this.model = bookService.get(id); } } public int getId() { return this.id; } // 處理不帶 id 參數的 GET 請求 // 進入首頁 public HttpHeaders index() { list = bookService.getAll(); return new DefaultHttpHeaders("index") .disableCaching(); } // 處理不帶 id 參數的 GET 請求 // 進入添加新圖書。 public String editNew() { // 創建一個新圖書 model = new Book(); return "editNew"; } // 處理不帶 id 參數的 POST 請求 // 保存新圖書 public HttpHeaders create() { // 保存圖書 bookService.saveOrUpdate(model); addActionMessage("添加圖書成功"); return new DefaultHttpHeaders("success") .setLocationId(model.getId()); } // 處理帶 id 參數的 GET 請求 // 顯示指定圖書 public HttpHeaders show() { return new DefaultHttpHeaders("show"); } // 處理帶 id 參數、且指定操作 edit 資源的 GET 請求 // 進入編輯頁面 (book-edit.jsp) public String edit() { return "edit"; } // 處理帶 id 參數的 PUT 請求 // 修改圖書 public String update() { bookService.saveOrUpdate(model); addActionMessage("圖書編輯成功!"); return "success"; } // 處理帶 id 參數,且指定操作 deleteConfirm 資源的方法 // 進入刪除頁面 (book-deleteConfirm.jsp) public String deleteConfirm() { return "deleteConfirm"; } // 處理帶 id 參數的 DELETE 請求 // 刪除圖書 public String destroy() { bookService.remove(id); addActionMessage("成功刪除 ID 為" + id + "的圖書!"); return "success"; } // 實現 ModelDriven 接口必須實現的 getModel 方法 public Object getModel() { return (list != null ? list : model); } } |
上面 Action 代碼中粗體字代碼定義了 7 個方法,這 7 個方法正是前面提到的標準方法。除此之外,該 Action 里還包含一個額外的 deleteConfirm() 方法,這個方法用于處理帶 id 參數、且指定操作 deleteConfirm 資源的 GET 請求。也就是說,當用戶請求 /book/1/deleteConfirm 時,該請求將由該方法負責處理。實際上,RestActionMapper 不僅可以將對 /book/1/edit 的請求映射到 Book 控制器的 edit() 方法,而 1 將作為 id 請求參數。實際上,它可以將任意 /book/1/xxx 的請求映射到 Book 控制器的 xxx() 方法,而 1 是請求參數。上面 Action 類使用了 @Results 進行修飾,這表明當 Action 的任何方法返回“success”邏輯視圖時,系統將重定向到 book.action。
可能有讀者會對 index()、create()、show() 三個方法的返回值感到疑惑:它們不再直接返回普通字符串作為邏輯視圖名字,而是返回一個以字符串為參數的 DefaultHttpHeaders 對象?其實讀者不必對 DefaultHttpHeaders 感到疑惑,其實 DefaultHttpHeaders 只是普通字符串的加強形式,用于 REST 對處理結果進行更多額外的控制。當 Action 類的處理方法返回字符串作為邏輯視圖時,Struts 2 只能將其當成一個簡單的視圖名,僅能根據該視圖名映射到實際視圖資源,僅此而已。如果使用 DefaultHttpHeaders 作為邏輯視圖,DefaultHttpHeaders 除了可以包含普通字符串作為邏輯視圖名之外,還可以額外增加更多的控制數據,從而可以增強對 Response 的控制。關于 HttpHeaders 和 DefaultHttpHeaders 的介紹請參考 REST 插件的 API。
還有一點需要指出,上面的 BookController 控制器實現類的類名并不以 Action 結尾,而是以 Controller 結尾,因此我們可以在 struts.xml 文件中配置如下常量:
<!-- 指定控制器類的后綴為 Controller --> <constant name="struts.convention.action.suffix" value="Controller"/> 本應用里的 struts.xml 文件如下: 程序清單:codes\12\12.6\BookShow\WEB-INF\src\struts.xml <?xml version="1.0" encoding="GBK" ?> <!-- 指定 Struts 2 配置文件的 DTD 信息 --> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.1//EN" "http://struts.apache.org/dtds/struts-2.1.dtd"> <!-- 指定 Struts 2 配置文件的根元素 --> <struts> <constant name="struts.i18n.encoding" value="GBK"/> <!-- 指定控制器類的后綴為 Controller --> <constant name="struts.convention.action.suffix" value="Controller"/> <constant name="struts.convention.action.mapAllMatches" value="true"/> <!-- 指定 Action 所在包繼承的父包 --> <constant name="struts.convention.default.parent.package" value="rest-default"/> </struts> |
![]() ![]() |
![]()
|
定義了上面 Action 之后,接下來應該為這些 Action 提供視圖頁面了,根據 Convention 插件的約定,所有視圖頁面都應該放在 WEB-INF\content 目錄下,例如當用戶向 /book.action 發送請求時,該請求將由 BookController 的 index() 方法進行處理,該方法處理結束后返回“index”字符串,也就是將會使用 WEIN-INF\content\book-index.jsp 頁面作為視圖資源。下面是 book-index.jsp 頁面的代碼:
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %> <%@taglib prefix="s" uri="/struts-tags" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title> 圖書展示系統 </title> <link href="<%=request.getContextPath() %>/css/demo.css" rel="stylesheet" type="text/css" /> </head> <body> <s:actionmessage /> <table> <tr> <th> 圖書 ID</th> <th> 書名 </th> <th> 價格 </th> <th> 操作 </th> </tr> <s:iterator value="model"> <tr> <td><s:property value="id"/></td> <td>${name}</td> <td>${price}</td> <td><a href="book/${id}"> 查看 </a> | <a href="book/${id}/edit"> 編輯 </a> | <a href="book/${id}/deleteConfirm"> 刪除 </a></td> </tr> </s:iterator> </table> <a href="<%=request.getContextPath() %>/book/new"> 創建新圖書 </a> </body> </html> |
上面 JSP 頁面非常簡單,它負責迭代輸出 Action 里包含的集合數據,向該應用 book.action 發送請求將看到如圖 1 所示頁面。
圖 1 使用 Struts 2 開發的 REST 服務

Struts 2 的 REST 插件支持一種資源具有多少表示形式,當瀏覽者向 book.xml 發送請求將可以看到如圖 2 所示頁面。
圖 2 REST 服務的 XML 形式

從圖 2 可以看出,該頁面正是 Action 所包含的全部數據,當使用 XML 顯示時 REST 插件將會負責把這些數據轉換成 XML 文檔。
除此之外,REST 插件還提供了 JSON 格式的顯示方式,當開發者向 book.json 發送請求將看到如圖 3 所示頁面。
圖 3 REST 服務的 JSON 形式

Struts 2 的 REST 插件默認支持 XHTML、XML 和 JSON 三種形式的數據。
當瀏覽者單擊頁面右邊的“編輯”鏈接,將會向 book/idVal/edit 發送請求,這是一個包含 ID 請求參數、且指定操作 edit 資源的請求,因此將由 BookController 的 edit() 方法負責處理,處理結束后進入 book-edit.jsp 頁面。瀏覽器里將看到如圖 4 所示頁面。
圖 4 編輯指定圖書

該頁面單擊“修改”按鈕時需要修改圖書信息,也就是需要使用 PUT 操作,但由于 HTML 不支持 PUT 操作,因此需要為該表單頁增加一個額外的請求參數:_method,該請求參數的值為 put。該表單頁的代碼如下:
<%@ page contentType="text/html; charset=GBK" language="java" errorPage="" %> <%@taglib prefix="s" uri="/struts-tags" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title> 編輯 ID 為 <s:property value="id"/> 的圖書 </title> <link href="<%=request.getContextPath() %>/css/demo.css" rel="stylesheet" type="text/css" /> </head> <body> <s:form method="post" action="%{#request.contextPath}/book/%{id}"> <!-- 增加 _method 請求參數,參數值為 put 用于模擬 PUT 操作 --> <s:hidden name="_method" value="put" /> <table> <s:textfield name="id" label="圖書 ID" disabled="true"/> <s:textfield name="name" label="書名"/> <s:textfield name="price" label="價格" /> <tr> <td colspan="2"> <s:submit value="修改"/> </td> </table> </s:form> <a href="<%=request.getContextPath() %>/book"> 返回首頁 </a> </body> </html> |
該表單將提交給 BookController 的 update() 方法處理,update() 方法將負責修改系統里指定 ID 對應的圖書信息。
與之類似的是,當請求需要執行 DELETE 操作時,一樣需要增加名為 _method 的請求參數,并將該請求參數值設置為 delete。
- “編寫 REST 服務”(developerWorks,2007 年 11 月):本教程討論了 REST 和 Atom 發布協議(Atom Publishing Protocol,APP)的概念,并展示如何在服務中進行應用。
- “構建 RESTful Web 服務”(developerWorks,2008 年 8 月):本教程從 REST 的基本概念開始,逐步指導您使用 Restlet 框架構建應用程序。
- “基于 REST 的 Web 服務:基礎”(developerWorks,2008 年 12 月): 在本文中,Alex Rodriguez 將向您介紹 REST 的基本原理。
- “進一步提升 Struts 2 對 Velocity 的支持力度”(developerWorks,2007 年 7 月):本文主要介紹如何解決 Struts 2 和最新版本的 Velocity 結合的問題以及進一步提升 Struts 2 對 Velocity 的支持力度。
- “基于 Struts 2 構建 WebSphere Portal 上的 Portlet 應用”(developerWorks,2008 年 2 月):本文的目的就是通過在 IBM 的 WebSphere Portal Server 上開發和部署一個基于 Struts 2 的 Porlet 應用,向讀者介紹利用 Struts 2 進行 Portlet 應用開發的優勢和關鍵流程。
- “基于 Struts 2 攔截器實現細粒度的基于角色的存取控制”(developerWorks,2008 年 9 月):本文介紹如何利用 Struts 2 攔截器來為 Java Web 應用添加應用管理的基于角色的存取控制的設計和實現方法。
- 參考本文作者李剛編著的圖書:《 Struts 2.1 權威指南——基于 WebWork 核心的 MVC 開發》。
- 參考:Struts 2 官方站點。
- 閱讀技術文章:Go Light with Apache Struts 2 and REST。
- Java 技術專區:尋找 Java 編程各方面的技術文章。
![]() |
||
|
![]() |
李剛,從事 Java EE 應用開發近 10 年。曾任 LITEON 公司的 J2EE 技術主管,負責該公司的企業信息化平臺的架構設計。曾任廣州電信、廣東龍泉科技等公司的技術培訓教師。瘋狂 Java 聯盟(http://www.crazyit.org)站長。瘋狂 Java 實訓營創始人,瘋狂 Java 體系圖書作者,曾任東方標準廣州中心軟件教學總監,曾兼任廣東技術師范學院計算機科學系的兼職副教授。國內知名IT技術作家,已出版《瘋狂 Java 講義》、《輕量級 Java EE 企業應用實戰》、《瘋狂 Ajax 講義》、《Struts 2.1 權威指南》、《Ruby On Rails 敏捷開發最佳實踐》等著作。 |
posted on 2010-03-11 11:06 小卓 閱讀(508) 評論(0) 編輯 收藏 所屬分類: struts