REST 是英文 Representational State Transfer 的縮寫,這個術(shù)語由 Roy Thomas Fielding 博士在他的論文《Architectural Styles and the Design of Network-based Software Architectures》中提出。從這篇論文的標(biāo)題可以看出:REST 是一種基于網(wǎng)絡(luò)的軟件架構(gòu)風(fēng)格。
提示:國內(nèi)很多網(wǎng)絡(luò)資料將 REST 翻譯為“表述性狀態(tài)轉(zhuǎn)移”,不過筆者對這個翻譯不太認(rèn)同。因為這個專業(yè)術(shù)語無法傳達(dá) REST 的含義,讀者可以先不要理會 REST 到底該如何翻譯,盡量先去理解 REST 是什么?有什么用?然后再來看這個術(shù)語的翻譯。關(guān)于 Roy Thomas Fielding 博士的原文參見如下地址:http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm。
REST 架構(gòu)是針對傳統(tǒng) Web 應(yīng)用提出的一種改進(jìn),是一種新型的分布式軟件設(shè)計架構(gòu)。對于異構(gòu)系統(tǒng)如何進(jìn)行整合的問題,目前主流做法都集中在使用 SOAP、WSDL 和 WS-* 規(guī)范的 Web Services。而 REST 架構(gòu)實際上也是解決異構(gòu)系統(tǒng)整合問題的一種新思路。
如果開發(fā)者在開發(fā)過程中能堅持 REST 原則,將可以得到一個使用了優(yōu)質(zhì) Web 架構(gòu)的系統(tǒng),從而為系統(tǒng)提供更好的可伸縮性,并降低開發(fā)難度。關(guān)于 REST 架構(gòu)的主要原則如下:
僅從上面幾條原則來看 REST 架構(gòu),其實依然比較難以理解,下面筆者將從如下二個方面來介紹 REST。
![]() ![]() |
![]()
|
現(xiàn)在的 Web 應(yīng)用上包含了大量信息,但這些信息都被隱藏在 HTML、CSS 和 JavaScript 代碼中,對于普通瀏覽者而言,他們進(jìn)入這個系統(tǒng)時無法知道該系統(tǒng)包含哪些頁面;對于一個需要訪問該系統(tǒng)資源的第三方系統(tǒng)而言,同樣無法明白這個系統(tǒng)包含多少功能和信息。
![]() |
|
從 REST 架構(gòu)的角度來看,該系統(tǒng)里包含的所有功能和信息,都可被稱為資源(Resource),REST 架構(gòu)中的資源包含靜態(tài)頁面、JSP 和 Servlet 等,該應(yīng)用暴露在網(wǎng)絡(luò)上的所有功能和信息都可被稱為資源。
除此之外,REST 架構(gòu)規(guī)范了應(yīng)用資源的命名方式,REST 規(guī)定對應(yīng)用資源使用統(tǒng)一的命名方式:REST 系統(tǒng)中的資源必須統(tǒng)一命名和規(guī)劃,REST 系統(tǒng)由使用 URI(Uniform Resource Identifier,即統(tǒng)一資源標(biāo)識符)命名的資源組成。由于 REST 對資源使用了基于 URI 的統(tǒng)一命名,因此這些信息就自然地暴露出來了,從而避免 “信息地窖”的不良后果。
對于當(dāng)今最常見的網(wǎng)絡(luò)應(yīng)用來說,資源標(biāo)識符就是 URI,資源的使用者則根據(jù) URI 來操作應(yīng)用資源。當(dāng) URI 發(fā)生改變時,表明客戶機(jī)所使用的資源發(fā)生了改變。
從資源的角度來看,當(dāng)客戶機(jī)操作不同的資源時,資源所在的 Web 頁(將 Web 頁當(dāng)成虛擬的狀態(tài)機(jī)來看)的狀態(tài)就會發(fā)生改變、遷移(Transfer),這就是 REST 術(shù)語中 ST(State Tranfer)的由來了。
客戶機(jī)為了操作不同狀態(tài)的資源,則需要發(fā)送一些 Representational 的數(shù)據(jù),這些數(shù)據(jù)包含必要的交互數(shù)據(jù),以及描述這些數(shù)據(jù)的元數(shù)據(jù)。這就是 REST 術(shù)語中 RE(Representational)的由來了。理解了這個層次之后,至于 REST 如何翻譯、或是否真正給它一個中文術(shù)語,讀者可自行決定。
![]() ![]() |
![]()
|
對于 REST 架構(gòu)的服務(wù)器端而言,它提供的是資源,但同一資源具有多種表現(xiàn)形式(可通過在 HTTP Content-type 頭中包含關(guān)于數(shù)據(jù)類型的元數(shù)據(jù))。如果客戶程序完全支持 HTTP 應(yīng)用協(xié)議,并能正確處理 REST 架構(gòu)的標(biāo)準(zhǔn)數(shù)據(jù)格式,那么它就可以與世界上任意一個 REST 風(fēng)格的用戶交互。這種情況不僅適用于從服務(wù)器端到客戶端的數(shù)據(jù),反之亦然——倘若從客戶端傳來的數(shù)據(jù)符合 REST 架構(gòu)的標(biāo)準(zhǔn)數(shù)據(jù)格式,那么服務(wù)器端也可以正確處理數(shù)據(jù),而不去關(guān)心客戶端的類型。
典型情況下,REST 風(fēng)格的資源能以 XHTML、XML 和 JSON 三種形式存在,其中 XML 格式的數(shù)據(jù)是 WebServices 技術(shù)的數(shù)據(jù)交換格式,而 JSON 則是另一種輕量級的數(shù)據(jù)交換格式;至于 XHTML 格式則主要由瀏覽器負(fù)責(zé)呈現(xiàn)。當(dāng)服務(wù)器為所有資源提供多種表現(xiàn)形式之后,這些資源不僅可以被標(biāo)準(zhǔn) Web 瀏覽器所使用,還可以由 JavaScript 通過 Ajax 技術(shù)調(diào)用,或者以 RPC(Remote Procedure Call)風(fēng)格調(diào)用,從而變成 REST 風(fēng)格的 WebServices。
REST 架構(gòu)除了規(guī)定服務(wù)器提供資源的方式之外,還推薦客戶端使用 HTTP 作為 Generic Connector Interface(也就是通用連接器接口),而 HTTP 則把對一個 URI 的操作限制在了 4 個之內(nèi):GET、POST、PUT 和 DELETE。通過使用通用連接器接口對資源進(jìn)行操作的好處是保證系統(tǒng)提供的服務(wù)都是高度解耦的,從而簡化了系統(tǒng)開發(fā),改善了系統(tǒng)的交互性和可重用性。
REST 架構(gòu)要求客戶端的所有的操作在本質(zhì)上是無狀態(tài)的,即從客戶到服務(wù)器的每個 Request 都必須包含理解該 Request 的所有必需信息。這種無狀態(tài)性的規(guī)范提供了如下幾點好處:
當(dāng)然,無狀態(tài)性會使得服務(wù)器不再保存 Request 的狀態(tài)數(shù)據(jù),因此需要在一系列 Request 中發(fā)送重復(fù)數(shù)據(jù),從而提高了系統(tǒng)的通信成本。為了改善無狀態(tài)性帶來的性能下降,REST 架構(gòu)填加了緩存約束。緩存約束允許隱式或顯式地標(biāo)記一個 Response 中的數(shù)據(jù),這樣就賦予了客戶端緩存 Response 數(shù)據(jù)的功能,這樣就可以為以后的 Request 共用緩存的數(shù)據(jù),部分或全部的消除一些交互,增加了網(wǎng)絡(luò)的效率。但是用于客戶端緩存了信息,也就同時增加了客戶端與服務(wù)器數(shù)據(jù)不一致的可能,從而降低了可靠性。
![]() ![]() |
![]()
|
![]() |
|
從 Struts 2.1 開始,Struts 2 改為使用 Convention 插件來支持零配置。Convention 插件徹底地拋棄了配置信息,不僅不需要使用 struts.xml 文件進(jìn)行配置,甚至不需要使用 Annotation 進(jìn)行配置。而是由 Struts 2 根據(jù)約定來自動配置。
Convention 這個單詞的翻譯過來就是“約定”的意思。有 Ruby On Rails 開發(fā)經(jīng)驗的讀者知道 Rails 有一條重要原則:約定優(yōu)于配置。Rails 開發(fā)者只需要按約定開發(fā) ActiveRecord、ActiveController 即可,無需進(jìn)行配置。很明顯,Struts 2 的 Convention 插件借鑒了 Rails 的創(chuàng)意,甚至連插件的名稱都借鑒了“約定優(yōu)于配置”原則。
由于 Struts 2 的 Convention 插件的主要特點是“約定優(yōu)于配置”,當(dāng)我們已經(jīng)習(xí)慣了 Struts 2 的基本開發(fā)方法之后,如果希望改為使用 Convention 插件也非常容易,我們只要放棄 Stuts 2.1 應(yīng)用原有的配置文件,改為按 Convention 插件的約定來定義 Action 即可。
以 Convention 插件為基礎(chǔ),Struts 2.1 又新增了 REST 插件,允許 Struts 2 應(yīng)用對外提供 REST 服務(wù)。REST 插件也無需使用 XML 進(jìn)行配置管理。Struts 2.1 通過 REST 插件完全可以提供讓人和機(jī)器客戶端共同使用的資源,并支持 Ruby On Rails 風(fēng)格的 URL。
![]() ![]() |
![]()
|
從本質(zhì)上來看,Struts 2 依然是一個 MVC 框架,最初設(shè)計 Struts 2 時并沒有按 REST 架構(gòu)進(jìn)行設(shè)計,因此 Struts 2 本質(zhì)上并不是一個 REST 框架。由于 Struts 2 提供了良好的可擴(kuò)展性,因此允許通過 REST 插件將其擴(kuò)展成支持 REST 的框架。REST 插件的核心是 RestActionMapper,它負(fù)責(zé)將 Rails 風(fēng)格的 URL 轉(zhuǎn)換為傳統(tǒng)請求的 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 說明,我們發(fā)現(xiàn)它可接受如下幾個參數(shù):
在 RestActionMapper 的方法列表中,我們看到 setIdParameterName、setIndexMethodName、setGetMethodName、setPostMethodName、setPutMethodName、setDeleteMethodName、setEditMethodName、setNewMethodName 等方法,這些方法對應(yīng)為上面列出的方法提供 setter 支持。
通常情況下,我們沒有必要改變 RestActionMapper 的參數(shù),直接使用這些參數(shù)的默認(rèn)值就可支持 Rails 風(fēng)格的 REST。根據(jù)前面介紹可以看出:支持 REST 風(fēng)格的 Action 至少包含如下 7 個方法:
如果請求需要向服務(wù)器發(fā)送 id 請求參數(shù),直接將請求參數(shù)的值附加在 URL 中即可。表 1 顯示了 RestActionMapper 對不同 HTTP 請求的處理結(jié)果。
HTTP 方法 | URI | 調(diào)用 Action 的方法 | 請求參數(shù) |
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 |
不幸地是,標(biāo)準(zhǔn) HTML 語言目前根本不支持 PUT 和 DELETE 兩個操作,為了彌補(bǔ)這種不足,REST 插件允許開發(fā)者提交請求時額外增加一個 _method 請求參數(shù),該參數(shù)值可以為 PUT 或 DELETE,用于模擬 HTTP 協(xié)議的 PUT 和 DELETE 操作。
![]() ![]() |
![]()
|
安裝 REST 插件非常簡單,只需按如下步驟進(jìn)行即可:
對于第三個步驟而言,開發(fā)者完全可以不設(shè)置該常量,如果開發(fā)者不設(shè)置該常量,則意味著開發(fā)者必須通過 Annotation 為每個 Action 類設(shè)置父包。
![]() ![]() |
![]()
|
在實現(xiàn)支持 REST 的 Action 之前,我們先為系統(tǒng)提供一個 Model 類:Book,該 Book 類非常簡單,代碼如下:
public class Book { private Integer id; private String name; private double price; // 無參數(shù)的構(gòu)造器 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 類提供一個業(yè)務(wù)邏輯組件:BookService。為了簡單起見,BookService 類不再依賴 DAO 組件訪問數(shù)據(jù)庫,而是直接操作內(nèi)存中的 Book 數(shù)組——簡單地說,本系統(tǒng)中狀態(tài)是瞬態(tài)的,沒有持久化保存,應(yīng)用運行過程中這些狀態(tài)一直存在,但一旦重啟該應(yīng)用,則系統(tǒng)狀態(tài)丟失。下面是 BookService 類的代碼:
public class BookService { private static Map<Integer , Book> books = new HashMap<Integer , Book>(); // 保留下本圖書的 ID private static int nextId = 5; // 以內(nèi)存中的數(shù)據(jù)模擬數(shù)據(jù)庫的持久存儲 static { books.put(1 , new Book(1 , "瘋狂 Java 講義" , 99)); books.put(2 , new Book(2 , "輕量級 Java EE 企業(yè)應(yīng)用實戰(zhàn)" , 89)); books.put(3 , new Book(3 , "瘋狂 Ajax 講義", 78)); books.put(4 , new Book(4 , "Struts 2 權(quán)威指南" , 79)); } // 根據(jù) ID 獲取 public Book get(int id) { return books.get(id); } // 獲取系統(tǒng)中全部圖書 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 個方法,用于實現(xiàn)對 Book 對象的 CRUD 操作。
下面開始定義支持 REST 的 Action 類了,這個 Action 類與前面介紹 Struts 2 的普通 Action 存在一些差異——因為該 Action 不再用 execute() 方法來處理用戶請求,而是使用前面介紹的 7 個標(biāo)準(zhǔn)方法來處理用戶請求。除此之外,該 Action 總是需要處理 id 請求參數(shù),因此必須提供 id 請求參數(shù),并為之提供對應(yīng)的 setter 和 getter 方法。
因為本系統(tǒng)已經(jīng)提供了 Book Model 類,并且為了更好的模擬 Rails 中 ActiveController(Controller)直接訪問 ActiveRecord(Model)的方式,本系統(tǒng)采用了 ModelDriven 的開發(fā)方式,下面是本系統(tǒng)中支持 REST 的 Action 類的代碼。
// 定義返回 success 時重定向到 book Action @Results(@Result(name="success" , type="redirectAction" , params = {"actionName" , "book"})) public class BookController extends ActionSupport implements ModelDriven<Object> { // 封裝 id 請求參數(shù)的屬性 private int id; private Book model = new Book(); private List<Book> list; // 定義業(yè)務(wù)邏輯組件 private BookService bookService = new BookService(); // 獲取 id 請求參數(shù)的方法 public void setId(int id) { this.id = id; // 取得方法時順帶初始化 model 對象 if (id > 0) { this.model = bookService.get(id); } } public int getId() { return this.id; } // 處理不帶 id 參數(shù)的 GET 請求 // 進(jìn)入首頁 public HttpHeaders index() { list = bookService.getAll(); return new DefaultHttpHeaders("index") .disableCaching(); } // 處理不帶 id 參數(shù)的 GET 請求 // 進(jìn)入添加新圖書。 public String editNew() { // 創(chuàng)建一個新圖書 model = new Book(); return "editNew"; } // 處理不帶 id 參數(shù)的 POST 請求 // 保存新圖書 public HttpHeaders create() { // 保存圖書 bookService.saveOrUpdate(model); addActionMessage("添加圖書成功"); return new DefaultHttpHeaders("success") .setLocationId(model.getId()); } // 處理帶 id 參數(shù)的 GET 請求 // 顯示指定圖書 public HttpHeaders show() { return new DefaultHttpHeaders("show"); } // 處理帶 id 參數(shù)、且指定操作 edit 資源的 GET 請求 // 進(jìn)入編輯頁面 (book-edit.jsp) public String edit() { return "edit"; } // 處理帶 id 參數(shù)的 PUT 請求 // 修改圖書 public String update() { bookService.saveOrUpdate(model); addActionMessage("圖書編輯成功!"); return "success"; } // 處理帶 id 參數(shù),且指定操作 deleteConfirm 資源的方法 // 進(jìn)入刪除頁面 (book-deleteConfirm.jsp) public String deleteConfirm() { return "deleteConfirm"; } // 處理帶 id 參數(shù)的 DELETE 請求 // 刪除圖書 public String destroy() { bookService.remove(id); addActionMessage("成功刪除 ID 為" + id + "的圖書!"); return "success"; } // 實現(xiàn) ModelDriven 接口必須實現(xiàn)的 getModel 方法 public Object getModel() { return (list != null ? list : model); } } |
上面 Action 代碼中粗體字代碼定義了 7 個方法,這 7 個方法正是前面提到的標(biāo)準(zhǔn)方法。除此之外,該 Action 里還包含一個額外的 deleteConfirm() 方法,這個方法用于處理帶 id 參數(shù)、且指定操作 deleteConfirm 資源的 GET 請求。也就是說,當(dāng)用戶請求 /book/1/deleteConfirm 時,該請求將由該方法負(fù)責(zé)處理。實際上,RestActionMapper 不僅可以將對 /book/1/edit 的請求映射到 Book 控制器的 edit() 方法,而 1 將作為 id 請求參數(shù)。實際上,它可以將任意 /book/1/xxx 的請求映射到 Book 控制器的 xxx() 方法,而 1 是請求參數(shù)。上面 Action 類使用了 @Results 進(jìn)行修飾,這表明當(dāng) Action 的任何方法返回“success”邏輯視圖時,系統(tǒng)將重定向到 book.action。
可能有讀者會對 index()、create()、show() 三個方法的返回值感到疑惑:它們不再直接返回普通字符串作為邏輯視圖名字,而是返回一個以字符串為參數(shù)的 DefaultHttpHeaders 對象?其實讀者不必對 DefaultHttpHeaders 感到疑惑,其實 DefaultHttpHeaders 只是普通字符串的加強(qiáng)形式,用于 REST 對處理結(jié)果進(jìn)行更多額外的控制。當(dāng) Action 類的處理方法返回字符串作為邏輯視圖時,Struts 2 只能將其當(dāng)成一個簡單的視圖名,僅能根據(jù)該視圖名映射到實際視圖資源,僅此而已。如果使用 DefaultHttpHeaders 作為邏輯視圖,DefaultHttpHeaders 除了可以包含普通字符串作為邏輯視圖名之外,還可以額外增加更多的控制數(shù)據(jù),從而可以增強(qiáng)對 Response 的控制。關(guān)于 HttpHeaders 和 DefaultHttpHeaders 的介紹請參考 REST 插件的 API。
還有一點需要指出,上面的 BookController 控制器實現(xiàn)類的類名并不以 Action 結(jié)尾,而是以 Controller 結(jié)尾,因此我們可以在 struts.xml 文件中配置如下常量:
<!-- 指定控制器類的后綴為 Controller --> <constant name="struts.convention.action.suffix" value="Controller"/> 本應(yīng)用里的 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 之后,接下來應(yīng)該為這些 Action 提供視圖頁面了,根據(jù) Convention 插件的約定,所有視圖頁面都應(yīng)該放在 WEB-INF\content 目錄下,例如當(dāng)用戶向 /book.action 發(fā)送請求時,該請求將由 BookController 的 index() 方法進(jìn)行處理,該方法處理結(jié)束后返回“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> 圖書展示系統(tǒng) </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"> 創(chuàng)建新圖書 </a> </body> </html> |
上面 JSP 頁面非常簡單,它負(fù)責(zé)迭代輸出 Action 里包含的集合數(shù)據(jù),向該應(yīng)用 book.action 發(fā)送請求將看到如圖 1 所示頁面。
Struts 2 的 REST 插件支持一種資源具有多少表示形式,當(dāng)瀏覽者向 book.xml 發(fā)送請求將可以看到如圖 2 所示頁面。
從圖 2 可以看出,該頁面正是 Action 所包含的全部數(shù)據(jù),當(dāng)使用 XML 顯示時 REST 插件將會負(fù)責(zé)把這些數(shù)據(jù)轉(zhuǎn)換成 XML 文檔。
除此之外,REST 插件還提供了 JSON 格式的顯示方式,當(dāng)開發(fā)者向 book.json 發(fā)送請求將看到如圖 3 所示頁面。
Struts 2 的 REST 插件默認(rèn)支持 XHTML、XML 和 JSON 三種形式的數(shù)據(jù)。
當(dāng)瀏覽者單擊頁面右邊的“編輯”鏈接,將會向 book/idVal/edit 發(fā)送請求,這是一個包含 ID 請求參數(shù)、且指定操作 edit 資源的請求,因此將由 BookController 的 edit() 方法負(fù)責(zé)處理,處理結(jié)束后進(jìn)入 book-edit.jsp 頁面。瀏覽器里將看到如圖 4 所示頁面。
該頁面單擊“修改”按鈕時需要修改圖書信息,也就是需要使用 PUT 操作,但由于 HTML 不支持 PUT 操作,因此需要為該表單頁增加一個額外的請求參數(shù):_method,該請求參數(shù)的值為 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 請求參數(shù),參數(shù)值為 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() 方法將負(fù)責(zé)修改系統(tǒng)里指定 ID 對應(yīng)的圖書信息。
與之類似的是,當(dāng)請求需要執(zhí)行 DELETE 操作時,一樣需要增加名為 _method 的請求參數(shù),并將該請求參數(shù)值設(shè)置為 delete。
![]() | ||
![]() | 李剛,從事 Java EE 應(yīng)用開發(fā)近 10 年。曾任 LITEON 公司的 J2EE 技術(shù)主管,負(fù)責(zé)該公司的企業(yè)信息化平臺的架構(gòu)設(shè)計。曾任廣州電信、廣東龍泉科技等公司的技術(shù)培訓(xùn)教師。 瘋狂 Java 聯(lián)盟(http://www.crazyit.org)站長。瘋狂 Java 實訓(xùn)營創(chuàng)始人,瘋狂 Java 體系圖書作者,曾任東方標(biāo)準(zhǔn)廣州中心軟件教學(xué)總監(jiān),曾兼任廣東技術(shù)師范學(xué)院計算機(jī)科學(xué)系的兼職副教授。國內(nèi)知名IT技術(shù)作家,已出版《瘋狂 Java 講義》、《輕量級 Java EE 企業(yè)應(yīng)用實戰(zhàn)》、《瘋狂 Ajax 講義》、《Struts 2.1 權(quán)威指南》、《Ruby On Rails 敏捷開發(fā)最佳實踐》等著作。 |