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

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

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 2.5 的註解式控制器

那麼 Spring 2.5 基於註解的控制器方法是如何融入這個圖景的呢?非常簡單:它本質上是 DispatcherServlet / DispatcherPortlet 支援的一種替代控制器型別,它不實現特定的介面,而是使用註解來表達特定處理器方法的請求對映。它主要是實現多動作控制器的下一代風格,取代了 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 的核心功能,即註解驅動的依賴注入:將呼叫此建構函式,並傳入從 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。

靈活的處理器方法簽名

對映是我稱之為“顯而易見”的部分,因為它非常清楚正在發生什麼。現在,不太明顯的部分是:處理器方法的簽名。這是一個非常靈活的事情,不像傳統的 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 物件的 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 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 基礎的發展方向一致——同時也透過我們新的 Spring Faces 模組特別關注 JSF。請密切關注!

獲取 Spring 資訊

訂閱 Spring 資訊,保持連線

訂閱

領先一步

VMware 提供培訓和認證,助力您的進步。

瞭解更多

獲取支援

Tanzu Spring 透過一個簡單的訂閱提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案。

瞭解更多

即將舉行的活動

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

檢視全部