領先一步
VMware 提供培訓和認證,助力您的進步。
瞭解更多Spring 2.5 引入了一種編寫註解式 Web MVC 控制器的方法,我們對此尚未進行太多部落格介紹... 我將藉此機會向大家概述當下 Spring MVC 的真正意義所在。
Spring MVC 本質上是一個請求分發器框架,具有 Servlet API 變體和 Portlet API 變體。它在其宿主環境(Servlet 或 Portlet)中緊密執行。可以將 Spring MVC 視為在 Servlet/Portlet 容器之上提供基礎設施和便利:例如,靈活的請求對映、控制器處理和檢視渲染階段的分離、資料繫結、補充 JSTL 的基本 JSP 標籤庫等。這些是複雜 HTTP 請求處理的基石。
Spring MVC 是一個非常靈活的框架:其核心 DispatcherServlet 不僅可以託管其原生控制器,還可以適應任何型別的動作。它可以託管處理基於 HTTP 的遠端協議的普通 HttpRequestHandler:這就是 Spring 使用者在定義 HTTP Invoker / Hessian / Burlap 服務匯出器或 Web Services 的 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 的核心功能,即註解驅動的依賴注入:將呼叫此建構函式,並傳入從 Spring ApplicationContext 按型別獲取的 ImageDatabase 型別的 Spring bean。在我們的例子中,這是來自應用程式服務層的核心 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 物件的 List,並將其以屬性名 "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 基礎的發展方向一致——同時也透過我們新的 Spring Faces 模組特別關注 JSF。請密切關注!