Spring 2.5 中的註解式 Web MVC 控制器

工程 | Juergen Hoeller | 2007 年 11 月 14 日 | ...

Spring 2.5 引入了一種編寫註解式 Web MVC 控制器的方法,我們對此的部落格介紹還不多……我將藉此機會概述一下 Spring MVC 目前的真正意義。

Spring MVC 本質上是一個請求分發器框架,它有 Servlet API 變體和 Portlet API 變體。它在其託管環境(Servlets 或 Portlets)中執行得非常緊密。將 Spring MVC 視為在 Servlet/Portlet 容器之上提供了基礎功能和便利性:例如,靈活的請求對映、控制器處理和檢視渲染階段的分離、資料繫結、補充 JSTL 的基本 JSP 標籤庫等。這是複雜 HTTP 請求處理的構建塊。

Spring MVC 是一個非常靈活的框架:它的核心 DispatcherServlet 不僅可以託管其原生控制器,還可以適應任何型別的動作。它可以託管處理基於 HTTP 的遠端協議的普通 HttpRequestHandlers:這就是 Spring 使用者在定義 HTTP invoker / Hessian / Burlap 服務匯出器,或 Web 服務的 XFire 匯出器時所利用的。DispatcherServlet 甚至可以託管任意第三方 Servlet,允許這些 Servlet 由 Spring 環境配置和管理。

Spring 2.5 的註解控制器

那麼,Spring 2.5 的基於註解的控制器方法是如何融入這個圖景的呢?很簡單:它本質上是 DispatcherServlet / DispatcherPortlet 支援的一種替代控制器型別,不實現特定的介面,而是使用註解來表達特定處理器方法的請求對映。它主要是實現多動作控制器(multi-action controllers)的下一代風格,取代了 Spring 經典的 MultiActionController 類。

讓我們來看一個例子,摘自 Spring 發行版附帶的“imagedb”示例應用程式。(注意:這是“imagedb”的 Spring 2.5 最終版本,與 RC 版本略有不同。)

@Controller
public class ImageController {

private ImageDatabase imageDatabase;

@Autowired
public ImageController(ImageDatabase imageDatabase) {
this.imageDatabase = imageDatabase;
}

@RequestMapping("/imageList")
public String showImageList(ModelMap model) {
model.addAttribute("images", this.imageDatabase.getImages());
return "imageList";
}

@RequestMapping("/imageContent")
public void streamImageContent(@RequestParam("name") String name, OutputStream outputStream)
throws IOException {

this.imageDatabase.streamImage(name, outputStream);
}

@RequestMapping("/imageUpload")
public String processImageUpload(
@RequestParam("name") String name, @RequestParam("description") String description,
@RequestParam("image") MultipartFile image) throws IOException {

this.imageDatabase.storeImage(name, image.getInputStream(), (int) image.getSize(), description);
return "redirect:imageList";
}

@RequestMapping("/clearDatabase")
public String clearDatabase() {
this.imageDatabase.clearDatabase();
return "redirect:imageList";
}
}

這個控制器類究竟做了什麼——它的設計目的是什麼?讓我們一步一步地來看……

@Controller 和 @RequestMapping

首先,該類被註解為 @Controller 樣板。這表明其方法應該被掃描以進行請求對映。它還允許透過 Spring 2.5 的元件掃描(<context:component-scan>)進行自動檢測,就像其他樣板 @Component、@Repository 和 @Service 一樣。在“imagedb”示例中,ImageController 仍然透過 <bean> 標籤顯式定義——這僅僅是因為自動檢測在控制器數量較多的情況下才真正有價值。

建構函式被標記為 @Autowired,並接受一個 ImageDatabase 型別的引數。這是核心 Spring 2.5 功能,即註解驅動的依賴注入:這個建構函式將被呼叫,傳入一個 ImageDatabase 型別的 Spring bean,該 bean 是透過型別從 Spring ApplicationContext 獲取的。在我們的例子中,這是應用程式服務層中的核心 ImageDatabase 服務。

實際的請求對映是透過方法級別的 @RequestMapping 註解表達的。每個對映都繫結到包含的 DispatcherServlet 中的特定 HTTP 路徑。對映路徑也可以從處理器方法名推斷出來,並且在型別級別上有一個常見的對映模式(例如,"*.image")——重用了我們熟悉的經典 MultiActionController 中的 InternalPathMethodNameResolver!

因此,當在型別級別使用 @RequestMapping 時,方法級別的註解將“縮小”特定處理器方法的對映。@RequestMapping 允許指定 HTTP 請求方法(例如,method = RequestMapping.GET)或特定的請求引數(例如,params = "action=save"),所有這些都可以縮小特定方法的型別級別對映。或者,型別級別的 @RequestMapping 也可以與經典的 Controller 介面實現相結合——例如 SimpleFormController 或 MultiActionController。

靈活的處理器方法簽名

對映可以說是“顯而易見”的部分,因為它很清楚發生了什麼。現在,不那麼顯而易見的部分是處理器方法簽名。這是一個非常靈活的事務,不被限制於像經典的 Controller 或 MultiActionController 那樣非常特定的簽名。當然,你可以使用標準的 HttpServletRequest / HttpServletResponse / ModelAndView 簽名,但真正的威力在於使用更具體的引數。

“imagedb”示例展示了幾個基本變體

@RequestMapping("/imageList")
public String showImageList(ModelMap model) {
model.addAttribute("images", this.imageDatabase.getImages());
return "imageList";
}

對於這個處理器方法,唯一需要解析的引數是 ModelMap。ModelMap 是 Spring 2.0 重新設計的 ModelAndView 物件的一部分,它封裝了一組將暴露給檢視的鍵值對。上面的程式碼只是呼叫 ImageDatabase 服務來載入 ImageDescriptor 物件列表,並將它們作為“images”屬性名暴露出來。或者,你可以呼叫不帶屬性名的 addAttribute 變體,在這種情況下,屬性名將根據給定的值型別推斷出來(在本例中是:“imageDescriptorList”)。

返回值是一個 String,簡單地表示要渲染的檢視的名稱。實際上,你可以編寫同一個方法,不帶引數,返回 ModelAndView——但上面的方法通常更容易閱讀,並且避免了對 ModelAndView 物件的依賴。(請注意,ModelMap 是“ui”包中的一個通用類,而 ModelAndView 是“web.servlet”包中一個相當具體的類。)

@RequestMapping("/imageContent")
public void streamImageContent(@RequestParam("name") String name, OutputStream outputStream)
throws IOException {

this.imageDatabase.streamImage(name, outputStream);
}

這個處理器方法展示了一個完全不同的用例。它的目的是將影像內容從資料庫流式傳輸到 HTTP 響應。它直接寫入響應,而不是轉發到檢視;因此它的返回型別是 void。它使用 Spring 2.5 的新 @RequestParam 註解,將 HTTP 請求引數作為方法引數接收,以及一個 OutputStream 型別的引數,用於訪問響應流的控制代碼。影像內容的實際載入再次委託給 ImageDatabase 服務。

或者,你可以使用更傳統的 HttpServletRequest / HttpServletResponse 簽名來實現相同的處理器方法,從而獲得對精確 HTTP 處理的更多控制。然而,這會引入與 Servlet API 的更強耦合,並且需要更多的單元測試工作。

@RequestMapping("/imageContent")
public void streamImageContent(HttpServletRequest request, HttpServletResponse response)
throws IOException {

this.imageDatabase.streamImage(request.getParameter("name"), response.getOutputStream());
}

這類處理器方法的目的應該已經很清楚了:它們是 HTTP 請求世界和服務層世界之間非常簡單的“橋樑”,用於適應請求引數和響應內容。讓我們以圖片上傳處理程式為例,看看一個高階示例。

@RequestMapping("/imageUpload")
public String processImageUpload(
@RequestParam("name") String name, @RequestParam("description") String description,
@RequestParam("image") MultipartFile image) throws IOException {

this.imageDatabase.storeImage(name, image.getInputStream(), (int) image.getSize(), description);
return "redirect:imageList";
}

基本目的仍然是接受幾個特定的 HTTP 請求引數,進行一些處理,然後返回一個檢視的名稱——在這種情況下,指示重定向到“imageList”路徑。然而,這個特定的方法處理的是多部分檔案上傳,這就是為什麼“image”引數被宣告為 MultipartFile 型別。Spring 的 @RequestParam 處理會自動將其解析為多部分元素,以便處理器方法能夠獲取檔案大小並作為 InputStream 訪問上傳的檔案內容。

有關注解處理器方法支援的引數型別的完整列表,請參閱 @RequestMapping javadoc

超越無狀態的多動作控制器

以上就是使用 Spring 2.5 的 Web 註解來實現多動作控制器。同樣的控制器風格也可以支援基本表單處理,取代了經典的 SimpleFormController。這可以在 Spring 2.5 版本的 PetClinic 中看到,它的所有表單控制器現在都以註解風格實現,展示了表單物件和基於 JavaBean 的資料繫結的使用。關於這些表單處理功能的討論將在後續博文中進行。

我在這裡總結一下 Spring MVC 的目的:正好是無狀態控制器、基本表單處理和靈活的檢視渲染。MVC 本質上是 Spring 核心 Web 支援中以排程為中心的模組,服務於許多不同型別的用例的執行時——並且在其之上構建了更高階的功能。在這方面,它類似於 Java EE 5 的 JSF 執行時,後者也主要作為一個基本的 Web 平臺來構建更高階的功能。

這就是 Spring Web Flow 的用武之地:SWF 是我們更高階的、面向會話的控制器引擎,對 MVC 檢視JSF 檢視 都有強大的支援。我強烈建議嘗試 SWF 來構建 Web 使用者介面,特別是在面對非平凡的導航和狀態管理需求時。Spring Web Flow 2.0 里程碑中有激動人心的進展,與 Spring 2.5 MVC 基礎的發展方向一致——但同時也特別側重於 JSF,透過我們新的 Spring Faces 模組。請繼續關注!

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視所有