領先一步
VMware 提供培訓和認證,助您加速進步。
瞭解更多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 發行版附帶的“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";
}
}
這個控制器類究竟做了什麼——它的設計目的是什麼?讓我們一步一步地來看……
建構函式被標記為 @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。
“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 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 模組。請繼續關注!