領先一步
VMware 提供培訓和認證,以加速您的進度。
了解更多在過去幾年中,REST 已經成為 SOAP/WSDL/WS-*-based 分散式架構的一個引人注目的替代方案。 因此,當我們開始規劃 Spring 的下一個主要版本(3.0 版)的工作時,我們很清楚必須專注於使 'RESTful' Web 服務和應用程式的開發更容易。 現在,什麼是以及什麼不是 'RESTful' 可能是一個全新的文章主題;在這篇文章中,我將採取更實際的方法,並專注於我們添加到 Spring MVC 的 @Controller 模型中的功能。
對我來說,REST 的工作大約在兩年前開始,就在閱讀了 Leonard Richardson 和 Sam Ruby 的 O'Reilly 撰寫的強烈推薦書籍RESTful Web Services之後。最初,我正在考慮將 REST 支援添加到 Spring Web Services,但在原型上工作了幾個星期後,我很清楚這不是一個很好的選擇。 特別是,我發現我必須從 Spring-MVC 複製大部分邏輯DispatcherServlet到 Spring-WS。 顯然,這不是前進的方向。
大約在同一時間,我們引入了 Spring MVC 的基於註釋的模型。 該模型顯然是對以前基於繼承的模型的改進。 當時另一個有趣的發展是 JAX-RS 規範的發展。 我的下一次嘗試是嘗試合併這兩個模型:嘗試將 @MVC 註釋與 JAX-RS 註釋結合起來,並能夠在以下環境中運行 JAX-RS 應用程式DispatcherServlet。 雖然我確實從這項工作中獲得了一個可行的原型,但結果並不令人滿意。 有一些技術問題我不會讓您感到厭煩,但最重要的是,對於已經習慣於 Spring MVC 2.5 的開發人員來說,這種方法感覺 '笨拙' 且不自然。
最後,我們決定將 RESTful 功能添加到 Spring MVC 本身的功能中。 顯然,這意味著與 JAX-RS 會有一些重疊,但至少對於現有和新的 Spring MVC 開發人員來說,編程模型將是令人滿意的。 此外,已經有三個 JAX-RS 實現提供 Spring 支援 (Jersey、RESTEasy 和 Restlet)。 將第四個添加到此列表中似乎並不是我們寶貴時間的最佳利用。
在 Spring 3.0 M1 中,我們透過以下方式引入了 URI 模板的使用@PathVariable註釋。 例如
@RequestMapping("/hotels/{hotelId}")
public String getHotel(@PathVariable String hotelId, Model model) {
List<Hotel> hotels = hotelService.getHotels();
model.addAttribute("hotels", hotels);
return "hotels";
}
當收到以下請求時/hotels/1,那個 1 將綁定到hotelId參數。 您可以選擇指定參數綁定的變數名稱,但是當您使用啟用偵錯功能的程式碼進行編譯時,則不需要這樣做:我們從參數名稱推斷路徑變數名稱。
您也可以有多個路徑變數,如下所示
@RequestMapping(value="/hotels/{hotel}/bookings/{booking}", method=RequestMethod.GET)
public String getBooking(@PathVariable("hotel") long hotelId, @PathVariable("booking") long bookingId, Model model) {
Hotel hotel = hotelService.getHotel(hotelId);
Booking booking = hotel.getBooking(bookingId);
model.addAttribute("booking", booking);
return "booking";
}
這將匹配像這樣的請求/hotels/1/bookings/2,例如。
您也可以將 Ant 樣式的路徑和路徑變數結合使用,如下所示
@RequestMapping(value="/hotels/*/bookings/{booking}", method=RequestMethod.GET)
public String getBooking(@PathVariable("booking") long bookingId, Model model) {
...
}
並且您也可以使用資料綁定
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
@RequestMapping("/hotels/{hotel}/dates/{date}")
public void date(@PathVariable("hotel") String hotel, @PathVariable Date date) {
...
}
以上將匹配/hotels/1/dates/2008-12-18,例如。
其中一個問題是Accept標頭是在 Web 瀏覽器中無法更改它,在 HTML 中。 例如,在 Firefox 中,它固定為 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
那麼如果您想連結到特定資源的 PDF 版本該怎麼辦? 查看檔案擴展名是一個很好的解決方法。 例如,http://example.com/hotels.pdf檢索飯店清單的 PDF 檢視,如下所示http://example.com/hotels,其 Accept 標頭為application/pdf.
這就是ContentNegotiatingViewResolver的作用:它包裝一個或多個其他的ViewResolver,查看Accept標頭或檔案擴展名,並解析與這些相對應的檢視。 在即將發布的部落格文章中,Alef Arendsen 將向您展示如何使用ContentNegotiatingViewResolver.
雖然 HTTP 定義了這四種方法,但 HTML 僅支援兩種方法:GET 和 POST。 幸運的是,有兩種可能的解決方法:您可以使用 JavaScript 執行 PUT 或 DELETE,或者只是使用 'real' 方法作為附加參數來執行 POST(在 HTML 表單中建模為隱藏的輸入欄位)。 後一個技巧就是HiddenHttpMethodFilter的作用。 此篩選器是在 Spring 3.0 M1 中引入的,並且是一個普通的 Servlet 篩選器。 因此,它可以與任何 Web 架構(不僅僅是 Spring MVC)結合使用。 只需將此篩選器添加到您的web.xml,並且具有隱藏的_method參數的 POST 將被轉換為相應的 HTTP 方法請求。
作為額外的獎勵,我們還在 Spring MVC 表單標籤中添加了對方法轉換的支援。 例如,以下摘自更新後的 Petclinic 範例的程式碼片段
<form:form method="delete">
<p class="submit"><input type="submit" value="Delete Pet"/></p>
</form:form>
實際上會執行 HTTP POST,'real' DELETE 方法隱藏在請求參數後面,以便被HiddenHttpMethodFilter提取。 因此,相應的 @Controller 方法是
@RequestMapping(method = RequestMethod.DELETE)
public String deletePet(@PathVariable int ownerId, @PathVariable int petId) {
this.clinic.deletePet(petId);
return "redirect:/owners/" + ownerId;
}
在 Spring 3.0 M1 中,我們引入了ShallowEtagHeaderFilter。 這是一個普通的 Servlet 篩選器,因此可以與任何 Web 架構結合使用。 顧名思義,該篩選器建立所謂的淺 ETag(而不是深 ETag,稍後會詳細介紹)。 它的工作方式非常簡單:該篩選器僅緩存呈現的 JSP(或其他內容)的內容,對其產生 MD5 哈希,並將其作為ETag標頭在回應中傳回。 下次客戶端發送相同資源的請求時,它將使用該哈希作為If-None-Match值。 篩選器會注意到這一點,再次呈現檢視,並比較兩個哈希。 如果它們相等,則傳回 304。 重要的是要注意,此篩選器不會節省處理能力,因為檢視仍會被呈現。 它唯一節省的是頻寬,因為呈現的回應不會透過網路發送回來。
深 ETag 更為複雜。 在這種情況下,ETag 基於底層網域物件、RDMBS 表等。 使用這種方法,除非底層資料已變更,否則不會產生任何內容。 不幸的是,以通用方式實現這種方法比淺 ETag 困難得多。 我們可能會在 Spring 的更高版本中添加對深 ETag 的支援,方法是依賴 JPA 的 @Version 註釋或 AspectJ 切面實例。