posts - 40, comments - 58, trackbacks - 0, articles - 0
            BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          Spring 2.5 注解驅(qū)動(dòng)的 Spring MVC

          Posted on 2008-06-20 16:13 Astro.Qi 閱讀(365) 評(píng)論(0)  編輯  收藏 所屬分類: Spring
          基于注解的配置有越來(lái)越流行的趨勢(shì),Spring 2.5 順應(yīng)這種趨勢(shì),為 Spring MVC 提供了完全基于注解的配置。本文將介紹 Spring 2.5 新增的 Sping MVC 注解功能,講述如何使用注解配置替換傳統(tǒng)的基于 XML 的 Spring MVC 配置。

          概述

          繼 Spring 2.0 對(duì) Spring MVC 進(jìn)行重大升級(jí)后,Spring 2.5 又為 Spring MVC 引入了注解驅(qū)動(dòng)功能。現(xiàn)在你無(wú)須讓 Controller 繼承任何接口,無(wú)需在 XML 配置文件中定義請(qǐng)求和 Controller 的映射關(guān)系,僅僅使用注解就可以讓一個(gè) POJO 具有 Controller 的絕大部分功能 —— Spring MVC 框架的易用性得到了進(jìn)一步的增強(qiáng).在框架靈活性、易用性和擴(kuò)展性上,Spring MVC 已經(jīng)全面超越了其它的 MVC 框架,伴隨著 Spring 一路高唱猛進(jìn),可以預(yù)見(jiàn) Spring MVC 在 MVC 市場(chǎng)上的吸引力將越來(lái)越不可抗拒。

          本文將介紹 Spring 2.5 新增的 Sping MVC 注解功能,講述如何使用注解配置替換傳統(tǒng)的基于 XML 的 Spring MVC 配置。





          回頁(yè)首


          一個(gè)簡(jiǎn)單的基于注解的 Controller

          使用過(guò)低版本 Spring MVC 的讀者都知道:當(dāng)創(chuàng)建一個(gè) Controller 時(shí),我們需要直接或間接地實(shí)現(xiàn) org.springframework.web.servlet.mvc.Controller 接口。一般情況下,我們是通過(guò)繼承 SimpleFormController 或 MultiActionController 來(lái)定義自己的 Controller 的。在定義 Controller 后,一個(gè)重要的事件是在 Spring MVC 的配置文件中通過(guò) HandlerMapping 定義請(qǐng)求和控制器的映射關(guān)系,以便將兩者關(guān)聯(lián)起來(lái)。

          來(lái)看一下基于注解的 Controller 是如何定義做到這一點(diǎn)的,下面是使用注解的 BbtForumController:


          清單 1. BbtForumController.java
                      package com.baobaotao.web;
                      import com.baobaotao.service.BbtForumService;
                      import org.springframework.beans.factory.annotation.Autowired;
                      import org.springframework.stereotype.Controller;
                      import org.springframework.web.bind.annotation.ModelAttribute;
                      import org.springframework.web.bind.annotation.RequestMapping;
                      import org.springframework.web.bind.annotation.RequestMethod;
                      import java.util.Collection;
                      @Controller                   //<——①
                      @RequestMapping("/forum.do")
                      public class BbtForumController {
                      @Autowired
                      private BbtForumService bbtForumService;
                      @RequestMapping //<——②
                      public String listAllBoard() {
                      bbtForumService.getAllBoard();
                      System.out.println("call listAllBoard method.");
                      return "listBoard";
                      }
                      }
                      

          從上面代碼中,我們可以看出 BbtForumController 和一般的類并沒(méi)有區(qū)別,它沒(méi)有實(shí)現(xiàn)任何特殊的接口,因而是一個(gè)地道的 POJO。讓這個(gè) POJO 與眾不同的魔棒就是 Spring MVC 的注解!

          在 ① 處使用了兩個(gè)注解,分別是 @Controller 和 @RequestMapping。在“使用 Spring 2.5 基于注解驅(qū)動(dòng)的 IoC”這篇文章里,筆者曾經(jīng)指出過(guò) @Controller、@Service 以及 @Repository 和 @Component 注解的作用是等價(jià)的:將一個(gè)類成為 Spring 容器的 Bean。由于 Spring MVC 的 Controller 必須事先是一個(gè) Bean,所以 @Controller 注解是不可缺少的。

          真正讓 BbtForumController 具備 Spring MVC Controller 功能的是 @RequestMapping 這個(gè)注解。@RequestMapping 可以標(biāo)注在類定義處,將 Controller 和特定請(qǐng)求關(guān)聯(lián)起來(lái);還可以標(biāo)注在方法簽名處,以便進(jìn)一步對(duì)請(qǐng)求進(jìn)行分流。在 ① 處,我們讓 BbtForumController 關(guān)聯(lián)“/forum.do”的請(qǐng)求,而 ② 處,我們具體地指定 listAllBoard() 方法來(lái)處理請(qǐng)求。所以在類聲明處標(biāo)注的 @RequestMapping 相當(dāng)于讓 POJO 實(shí)現(xiàn)了 Controller 接口,而在方法定義處的 @RequestMapping 相當(dāng)于讓 POJO 擴(kuò)展 Spring 預(yù)定義的 Controller(如 SimpleFormController 等)。

          為了讓基于注解的 Spring MVC 真正工作起來(lái),需要在 Spring MVC 對(duì)應(yīng)的 xxx-servlet.xml 配置文件中做一些手腳。在此之前,還是先來(lái)看一下 web.xml 的配置吧:


          清單 2. web.xml:?jiǎn)⒂?Spring 容器和 Spring MVC 框架
                      <?xml version="1.0" encoding="UTF-8"?>
                      <web-app xmlns="http://java.sun.com/xml/ns/javaee"
                      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                      xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                      http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
                      <display-name>Spring Annotation MVC Sample</display-name>
                      <!--  Spring 服務(wù)層的配置文件 -->
                      <context-param>
                      <param-name>contextConfigLocation</param-name>
                      <param-value>classpath:applicationContext.xml</param-value>
                      </context-param>
                      <!--  Spring 容器啟動(dòng)監(jiān)聽(tīng)器 -->
                      <listener>
                      <listener-class>org.springframework.web.context.ContextLoaderListener
                      </listener-class>
                      </listener>
                      <!--  Spring MVC 的Servlet,它將加載WEB-INF/annomvc-servlet.xml 的
                      配置文件,以啟動(dòng)Spring MVC模塊-->
                      <servlet>
                      <servlet-name>annomvc</servlet-name>
                      <servlet-class>org.springframework.web.servlet.DispatcherServlet
                      </servlet-class>
                      <load-on-startup>2</load-on-startup>
                      </servlet>
                      <servlet-mapping>
                      <servlet-name>annomvc</servlet-name>
                      <url-pattern>*.do</url-pattern>
                      </servlet-mapping>
                      </web-app>
                      

          web.xml 中定義了一個(gè)名為 annomvc 的 Spring MVC 模塊,按照 Spring MVC 的契約,需要在 WEB-INF/annomvc-servlet.xml 配置文件中定義 Spring MVC 模塊的具體配置。annomvc-servlet.xml 的配置內(nèi)容如下所示:

          清單 3. annomvc-servlet.xml

                      <?xml version="1.0" encoding="UTF-8"?>
                      <beans
                      xmlns="http://www.springframework.org/schema/beans"
                      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                      xmlns:p="http://www.springframework.org/schema/p"
                      xmlns:context="http://www.springframework.org/schema/context"
                      xsi:schemaLocation="http://www.springframework.org/schema/beans
                      http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
                      http://www.springframework.org/schema/context
                      http://www.springframework.org/schema/context/spring-context-2.5.xsd">
                      <!-- ①:對(duì)web包中的所有類進(jìn)行掃描,以完成Bean創(chuàng)建和自動(dòng)依賴注入的功能 -->
                      <context:component-scan base-package="com.baobaotao.web"/>
                      <!-- ②:?jiǎn)?dòng)Spring MVC的注解功能,完成請(qǐng)求和注解POJO的映射 -->
                      <bean class="org.springframework.web.servlet.mvc.annotation.
                      AnnotationMethodHandlerAdapter"/>
                      <!--  ③:對(duì)模型視圖名稱的解析,即在模型視圖名稱添加前后綴 -->
                      <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
                      p:prefix="/WEB-INF/jsp/" p:suffix=".jsp"/>
                      </beans>
                      

          因?yàn)?Spring 所有功能都在 Bean 的基礎(chǔ)上演化而來(lái),所以必須事先將 Controller 變成 Bean,這是通過(guò)在類中標(biāo)注 @Controller 并在 annomvc-servlet.xml 中啟用組件掃描機(jī)制來(lái)完成的,如 ① 所示。

          在 ② 處,配置了一個(gè) AnnotationMethodHandlerAdapter,它負(fù)責(zé)根據(jù) Bean 中的 Spring MVC 注解對(duì) Bean 進(jìn)行加工處理,使這些 Bean 變成控制器并映射特定的 URL 請(qǐng)求。

          而 ③ 處的工作是定義模型視圖名稱的解析規(guī)則,這里我們使用了 Spring 2.5 的特殊命名空間,即 p 命名空間,它將原先需要通過(guò) <property> 元素配置的內(nèi)容轉(zhuǎn)化為 <bean> 屬性配置,在一定程度上簡(jiǎn)化了 <bean> 的配置。

          啟動(dòng) Tomcat,發(fā)送 http://localhost/forum.do URL 請(qǐng)求,BbtForumController 的 listAllBoard() 方法將響應(yīng)這個(gè)請(qǐng)求,并轉(zhuǎn)向 WEB-INF/jsp/listBoard.jsp 的視圖頁(yè)面。





          回頁(yè)首


          讓一個(gè) Controller 處理多個(gè) URL 請(qǐng)求

          在低版本的 Spring MVC 中,我們可以通過(guò)繼承 MultiActionController 讓一個(gè) Controller 處理多個(gè) URL 請(qǐng)求。使用 @RequestMapping 注解后,這個(gè)功能更加容易實(shí)現(xiàn)了。請(qǐng)看下面的代碼:

          清單 3. 每個(gè)請(qǐng)求處理參數(shù)對(duì)應(yīng)一個(gè) URL

                      package com.baobaotao.web;
                      import com.baobaotao.service.BbtForumService;
                      import org.springframework.beans.factory.annotation.Autowired;
                      import org.springframework.stereotype.Controller;
                      import org.springframework.web.bind.annotation.RequestMapping;
                      @Controller
                      public class BbtForumController {
                      @Autowired
                      private BbtForumService bbtForumService;
                      @RequestMapping("/listAllBoard.do") // <—— ①
                      public String listAllBoard() {
                      bbtForumService.getAllBoard();
                      System.out.println("call listAllBoard method.");
                      return "listBoard";
                      }
                      @RequestMapping("/listBoardTopic.do") // <—— ②
                      public String listBoardTopic(int topicId) {
                      bbtForumService.getBoardTopics(topicId);
                      System.out.println("call listBoardTopic method.");
                      return "listTopic";
                      }
                      }
                      

          在這里,我們分別在 ① 和 ② 處為 listAllBoard() 和 listBoardTopic() 方法標(biāo)注了 @RequestMapping 注解,分別指定這兩個(gè)方法處理的 URL 請(qǐng)求,這相當(dāng)于將 BbtForumController 改造為 MultiActionController。這樣 /listAllBoard.do 的 URL 請(qǐng)求將由 listAllBoard() 負(fù)責(zé)處理,而 /listBoardTopic.do?topicId=1 的 URL 請(qǐng)求則由 listBoardTopic() 方法處理。

          對(duì)于處理多個(gè) URL 請(qǐng)求的 Controller 來(lái)說(shuō),我們傾向于通過(guò)一個(gè) URL 參數(shù)指定 Controller 處理方法的名稱(如 method=listAllBoard),而非直接通過(guò)不同的 URL 指定 Controller 的處理方法。使用 @RequestMapping 注解很容易實(shí)現(xiàn)這個(gè)常用的需求。來(lái)看下面的代碼:


          清單 4. 一個(gè) Controller 對(duì)應(yīng)一個(gè) URL,由請(qǐng)求參數(shù)決定請(qǐng)求處理方法
                      package com.baobaotao.web;
                      import com.baobaotao.service.BbtForumService;
                      import org.springframework.beans.factory.annotation.Autowired;
                      import org.springframework.stereotype.Controller;
                      import org.springframework.web.bind.annotation.RequestMapping;
                      @Controller
                      @RequestMapping("/bbtForum.do")  // <—— ① 指定控制器對(duì)應(yīng)URL請(qǐng)求
                      public class BbtForumController {
                      @Autowired
                      private BbtForumService bbtForumService;
                      // <—— ② 如果URL請(qǐng)求中包括"method=listAllBoard"的參數(shù),由本方法進(jìn)行處理
                      @RequestMapping(params = "method=listAllBoard")
                      public String listAllBoard() {
                      bbtForumService.getAllBoard();
                      System.out.println("call listAllBoard method.");
                      return "listBoard";
                      }
                      // <—— ③ 如果URL請(qǐng)求中包括"method=listBoardTopic"的參數(shù),由本方法進(jìn)行處理
                      @RequestMapping(params = "method=listBoardTopic")
                      public String listBoardTopic(int topicId) {
                      bbtForumService.getBoardTopics(topicId);
                      System.out.println("call listBoardTopic method.");
                      return "listTopic";
                      }
                      }
                      

          在類定義處標(biāo)注的 @RequestMapping 讓 BbtForumController 處理所有包含 /bbtForum.do 的 URL 請(qǐng)求,而 BbtForumController 中的請(qǐng)求處理方法對(duì) URL 請(qǐng)求的分流規(guī)則在 ② 和 ③ 處定義分流規(guī)則按照 URL 的 method 請(qǐng)求參數(shù)確定。所以分別在類定義處和方法定義處使用 @RequestMapping 注解,就可以很容易通過(guò) URL 參數(shù)指定 Controller 的處理方法了。

          @RequestMapping 注解中除了 params 屬性外,還有一個(gè)常用的屬性是 method,它可以讓 Controller 方法處理特定 HTTP 請(qǐng)求方式的請(qǐng)求,如讓一個(gè)方法處理 HTTP GET 請(qǐng)求,而另一個(gè)方法處理 HTTP POST 請(qǐng)求,如下所示:


          清單 4. 讓請(qǐng)求處理方法處理特定的 HTTP 請(qǐng)求方法
                      package com.baobaotao.web;
                      import com.baobaotao.service.BbtForumService;
                      import org.springframework.beans.factory.annotation.Autowired;
                      import org.springframework.stereotype.Controller;
                      import org.springframework.web.bind.annotation.RequestMapping;
                      import org.springframework.web.bind.annotation.RequestMethod;
                      @Controller
                      @RequestMapping("/bbtForum.do")
                      public class BbtForumController {
                      @RequestMapping(params = "method=createTopic",method = RequestMethod.POST)
                      public String createTopic(){
                      System.out.println("call createTopic method.");
                      return "createTopic";
                      }
                      }
                      

          這樣只有當(dāng) /bbtForum.do?method=createTopic 請(qǐng)求以 HTTP POST 方式提交時(shí),createTopic() 方法才會(huì)進(jìn)行處理。





          回頁(yè)首


          處理方法入?yún)⑷绾谓壎?URL 參數(shù)

          按契約綁定

          Controller 的方法標(biāo)注了 @RequestMapping 注解后,它就能處理特定的 URL 請(qǐng)求。我們不禁要問(wèn):請(qǐng)求處理方法入?yún)⑹侨绾谓壎?URL 參數(shù)的呢?在回答這個(gè)問(wèn)題之前先來(lái)看下面的代碼:

          清單 5. 按參數(shù)名匹配進(jìn)行綁定

                      @RequestMapping(params = "method=listBoardTopic")
                      //<—— ① topicId入?yún)⑹侨绾谓壎║RL請(qǐng)求參數(shù)的?
                      public String listBoardTopic(int topicId) {
                      bbtForumService.getBoardTopics(topicId);
                      System.out.println("call listBoardTopic method.");
                      return "listTopic";
                      }
                      

          當(dāng)我們發(fā)送 http://localhost//bbtForum.do?method=listBoardTopic&topicId=10 的 URL 請(qǐng)求時(shí),Spring 不但讓 listBoardTopic() 方法處理這個(gè)請(qǐng)求,而且還將 topicId 請(qǐng)求參數(shù)在類型轉(zhuǎn)換后綁定到 listBoardTopic() 方法的 topicId 入?yún)⑸稀6?listBoardTopic() 方法的返回類型是 String,它將被解析為邏輯視圖的名稱。也就是說(shuō) Spring 在如何給處理方法入?yún)⒆詣?dòng)賦值以及如何將處理方法返回值轉(zhuǎn)化為 ModelAndView 中的過(guò)程中存在一套潛在的規(guī)則,不熟悉這個(gè)規(guī)則就不可能很好地開(kāi)發(fā)基于注解的請(qǐng)求處理方法,因此了解這個(gè)潛在規(guī)則無(wú)疑成為理解 Spring MVC 框架基于注解功能的核心問(wèn)題。

          我們不妨從最常見(jiàn)的開(kāi)始說(shuō)起:請(qǐng)求處理方法入?yún)⒌念愋涂梢允?Java 基本數(shù)據(jù)類型或 String 類型,這時(shí)方法入?yún)磪?shù)名匹配的原則綁定到 URL 請(qǐng)求參數(shù),同時(shí)還自動(dòng)完成 String 類型的 URL 請(qǐng)求參數(shù)到請(qǐng)求處理方法參數(shù)類型的轉(zhuǎn)換。下面給出幾個(gè)例子:

          • listBoardTopic(int topicId):和 topicId URL 請(qǐng)求參數(shù)綁定;
          • listBoardTopic(int topicId,String boardName):分別和 topicId、boardName URL 請(qǐng)求參數(shù)綁定;
          ?

          特別的,如果入?yún)⑹腔緮?shù)據(jù)類型(如 int、long、float 等),URL 請(qǐng)求參數(shù)中一定要有對(duì)應(yīng)的參數(shù),否則將拋出 TypeMismatchException 異常,提示無(wú)法將 null 轉(zhuǎn)換為基本數(shù)據(jù)類型。

          另外,請(qǐng)求處理方法的入?yún)⒁部梢砸粋€(gè) JavaBean,如下面的 User 對(duì)象就可以作為一個(gè)入?yún)ⅲ?/p>
          清單 6. User.java:一個(gè) JavaBean
                      package com.baobaotao.web;
                      public class User {
                      private int userId;
                      private String userName;
                      //省略get/setter方法
                      public String toString(){
                      return this.userName +","+this.userId;
                      }
                      }
                      

          下面是將 User 作為 listBoardTopic() 請(qǐng)求處理方法的入?yún)ⅲ?/p>
          清單 7. 使用 JavaBean 作為請(qǐng)求處理方法的入?yún)?/strong>
                      @RequestMapping(params = "method=listBoardTopic")
                      public String listBoardTopic(int topicId,User user) {
                      bbtForumService.getBoardTopics(topicId);
                      System.out.println("topicId:"+topicId);
                      System.out.println("user:"+user);
                      System.out.println("call listBoardTopic method.");
                      return "listTopic";
                      }
                      

          這時(shí),如果我們使用以下的 URL 請(qǐng)求:http://localhost/bbtForum.do?method=listBoardTopic&topicId=1&userId=10&userName=tom

          topicId URL 參數(shù)將綁定到 topicId 入?yún)⑸希?userId 和 userName URL 參數(shù)將綁定到 user 對(duì)象的 userId 和 userName 屬性中。和 URL 請(qǐng)求中不允許沒(méi)有 topicId 參數(shù)不同,雖然 User 的 userId 屬性的類型是基本數(shù)據(jù)類型,但如果 URL 中不存在 userId 參數(shù),Spring 也不會(huì)報(bào)錯(cuò),此時(shí) user.userId 值為 0。如果 User 對(duì)象擁有一個(gè) dept.deptId 的級(jí)聯(lián)屬性,那么它將和 dept.deptId URL 參數(shù)綁定。

          通過(guò)注解指定綁定的 URL 參數(shù)

          如果我們想改變這種默認(rèn)的按名稱匹配的策略,比如讓 listBoardTopic(int topicId,User user) 中的 topicId 綁定到 id 這個(gè) URL 參數(shù),那么可以通過(guò)對(duì)入?yún)⑹褂?@RequestParam 注解來(lái)達(dá)到目的:

          清單 8. 通過(guò) @RequestParam 注解指定

                      package com.baobaotao.web;
                      import org.springframework.web.bind.annotation.RequestMapping;
                      import org.springframework.web.bind.annotation.RequestParam;
                      …
                      @Controller
                      @RequestMapping("/bbtForum.do")
                      public class BbtForumController {
                      @RequestMapping(params = "method=listBoardTopic")
                      public String listBoardTopic(@RequestParam("id") int topicId,User user) {
                      bbtForumService.getBoardTopics(topicId);
                      System.out.println("topicId:"+topicId);
                      System.out.println("user:"+user);
                      System.out.println("call listBoardTopic method.");
                      return "listTopic";
                      }
                      …
                      }
                      

          這里,對(duì) listBoardTopic() 請(qǐng)求處理方法的 topicId 入?yún)?biāo)注了 @RequestParam("id") 注解,所以它將和 id 的 URL 參數(shù)綁定。

          綁定模型對(duì)象中某個(gè)屬性

          Spring 2.0 定義了一個(gè) org.springframework.ui.ModelMap 類,它作為通用的模型數(shù)據(jù)承載對(duì)象,傳遞數(shù)據(jù)供視圖所用。我們可以在請(qǐng)求處理方法中聲明一個(gè) ModelMap 類型的入?yún)ⅲ琒pring 會(huì)將本次請(qǐng)求模型對(duì)象引用通過(guò)該入?yún)鬟f進(jìn)來(lái),這樣就可以在請(qǐng)求處理方法內(nèi)部訪問(wèn)模型對(duì)象了。來(lái)看下面的例子:


          清單 9. 使用 ModelMap 訪問(wèn)請(qǐng)示對(duì)應(yīng)的隱含模型對(duì)象
                      @RequestMapping(params = "method=listBoardTopic")
                      public String listBoardTopic(@RequestParam("id")int topicId,
                      User user,ModelMap model) {
                      bbtForumService.getBoardTopics(topicId);
                      System.out.println("topicId:" + topicId);
                      System.out.println("user:" + user);
                      //① 將user對(duì)象以currUser為鍵放入到model中
                      model.addAttribute("currUser",user);
                      return "listTopic";
                      }
                      

          對(duì)于當(dāng)次請(qǐng)求所對(duì)應(yīng)的模型對(duì)象來(lái)說(shuō),其所有屬性都將存放到 request 的屬性列表中。象上面的例子,ModelMap 中的 currUser 屬性將放到 request 的屬性列表中,所以可以在 JSP 視圖頁(yè)面中通過(guò) request.getAttribute(“currUser”) 或者通過(guò) ${currUser} EL 表達(dá)式訪問(wèn)模型對(duì)象中的 user 對(duì)象。從這個(gè)角度上看, ModelMap 相當(dāng)于是一個(gè)向 request 屬性列表中添加對(duì)象的一條管道,借由 ModelMap 對(duì)象的支持,我們可以在一個(gè)不依賴 Servlet API 的 Controller 中向 request 中添加屬性。

          在默認(rèn)情況下,ModelMap 中的屬性作用域是 request 級(jí)別是,也就是說(shuō),當(dāng)本次請(qǐng)求結(jié)束后,ModelMap 中的屬性將銷毀。如果希望在多個(gè)請(qǐng)求中共享 ModelMap 中的屬性,必須將其屬性轉(zhuǎn)存到 session 中,這樣 ModelMap 的屬性才可以被跨請(qǐng)求訪問(wèn)。

          Spring 允許我們有選擇地指定 ModelMap 中的哪些屬性需要轉(zhuǎn)存到 session 中,以便下一個(gè)請(qǐng)求屬對(duì)應(yīng)的 ModelMap 的屬性列表中還能訪問(wèn)到這些屬性。這一功能是通過(guò)類定義處標(biāo)注 @SessionAttributes 注解來(lái)實(shí)現(xiàn)的。請(qǐng)看下面的代碼:


          清單 10. 使模型對(duì)象的特定屬性具有 Session 范圍的作用域
                      package com.baobaotao.web;
                      …
                      import org.springframework.ui.ModelMap;
                      import org.springframework.web.bind.annotation.SessionAttributes;
                      @Controller
                      @RequestMapping("/bbtForum.do")
                      @SessionAttributes("currUser") //①將ModelMap中屬性名為currUser的屬性
                      //放到Session屬性列表中,以便這個(gè)屬性可以跨請(qǐng)求訪問(wèn)
                      public class BbtForumController {
                      …
                      @RequestMapping(params = "method=listBoardTopic")
                      public String listBoardTopic(@RequestParam("id")int topicId, User user,
                      ModelMap model) {
                      bbtForumService.getBoardTopics(topicId);
                      System.out.println("topicId:" + topicId);
                      System.out.println("user:" + user);
                      model.addAttribute("currUser",user); //②向ModelMap中添加一個(gè)屬性
                      return "listTopic";
                      }
                      }
                      

          我們?cè)?② 處添加了一個(gè) ModelMap 屬性,其屬性名為 currUser,而 ① 處通過(guò) @SessionAttributes 注解將 ModelMap 中名為 currUser 的屬性放置到 Session 中,所以我們不但可以在 listBoardTopic() 請(qǐng)求所對(duì)應(yīng)的 JSP 視圖頁(yè)面中通過(guò) request.getAttribute(“currUser”) 和 session.getAttribute(“currUser”) 獲取 user 對(duì)象,還可以在下一個(gè)請(qǐng)求所對(duì)應(yīng)的 JSP 視圖頁(yè)面中通過(guò) session.getAttribute(“currUser”) 或 ModelMap#get(“currUser”) 訪問(wèn)到這個(gè)屬性。

          這里我們僅將一個(gè) ModelMap 的屬性放入 Session 中,其實(shí) @SessionAttributes 允許指定多個(gè)屬性。你可以通過(guò)字符串?dāng)?shù)組的方式指定多個(gè)屬性,如 @SessionAttributes({“attr1”,”attr2”})。此外,@SessionAttributes 還可以通過(guò)屬性類型指定要 session 化的 ModelMap 屬性,如 @SessionAttributes(types = User.class),當(dāng)然也可以指定多個(gè)類,如 @SessionAttributes(types = {User.class,Dept.class}),還可以聯(lián)合使用屬性名和屬性類型指定:@SessionAttributes(types = {User.class,Dept.class},value={“attr1”,”attr2”})。

          上面講述了如何往ModelMap中放置屬性以及如何使ModelMap中的屬性擁有Session域的作用范圍。除了在JSP視圖頁(yè)面中通過(guò)傳統(tǒng)的方法訪問(wèn)ModelMap中的屬性外,讀者朋友可能會(huì)問(wèn):是否可以將ModelMap中的屬性綁定到請(qǐng)求處理方法的入?yún)⒅心兀看鸢甘强隙ǖ摹pring為此提供了一個(gè)@ModelAttribute的注解,下面是使用@ModelAttribute注解的例子:

          清單 11. 使模型對(duì)象的特定屬性具有 Session 范圍的作用域

                      package com.baobaotao.web;
                      import com.baobaotao.service.BbtForumService;
                      import org.springframework.beans.factory.annotation.Autowired;
                      import org.springframework.stereotype.Controller;
                      import org.springframework.ui.ModelMap;
                      import org.springframework.web.bind.annotation.RequestMapping;
                      import org.springframework.web.bind.annotation.RequestParam;
                      import org.springframework.web.bind.annotation.SessionAttributes;
                      import org.springframework.web.bind.annotation.ModelAttribute;
                      import javax.servlet.http.HttpServletRequest;
                      import javax.servlet.http.HttpSession;
                      @Controller
                      @RequestMapping("/bbtForum.do")
                      @SessionAttributes("currUser") //①讓ModelMap的currUser屬性擁有session級(jí)作用域
                      public class BbtForumController {
                      @Autowired
                      private BbtForumService bbtForumService;
                      @RequestMapping(params = "method=listBoardTopic")
                      public String listBoardTopic(@RequestParam("id")int topicId, User user,
                      ModelMap model) {
                      bbtForumService.getBoardTopics(topicId);
                      System.out.println("topicId:" + topicId);
                      System.out.println("user:" + user);
                      model.addAttribute("currUser",user); //②向ModelMap中添加一個(gè)屬性
                      return "listTopic";
                      }
                      @RequestMapping(params = "method=listAllBoard")
                      //③將ModelMap中的
                      public String listAllBoard(@ModelAttribute("currUser") User user) {
                      //currUser屬性綁定到user入?yún)⒅小?
                      bbtForumService.getAllBoard();
                      System.out.println("user:"+user);
                      return "listBoard";
                      }
                      }
                      

          在 ② 處,我們向 ModelMap 中添加一個(gè)名為 currUser 的屬性,而 ① 外的注解使這個(gè) currUser 屬性擁有了 session 級(jí)的作用域。所以,我們可以在 ③ 處通過(guò) @ModelAttribute 注解將 ModelMap 中的 currUser 屬性綁定以請(qǐng)求處理方法的 user 入?yún)⒅小?/p>

          所以當(dāng)我們先調(diào)用以下 URL 請(qǐng)求: http://localhost/bbtForum.do?method=listBoardTopic&id=1&userName=tom&dept.deptId=12

          以執(zhí)行l(wèi)istBoardTopic()請(qǐng)求處理方法,然后再訪問(wèn)以下URL: http://localhost/sample/bbtForum.do?method=listAllBoard

          你將可以看到 listAllBoard() 的 user 入?yún)⒁呀?jīng)成功綁定到 listBoardTopic() 中注冊(cè)的 session 級(jí)的 currUser 屬性上了。





          回頁(yè)首


          請(qǐng)求處理方法的簽名規(guī)約

          方法入?yún)?/span>

          我們知道標(biāo)注了 @RequestMapping 注解的 Controller 方法就成為了請(qǐng)求處理方法,Spring MVC 允許極其靈活的請(qǐng)求處理方法簽名方式。對(duì)于方法入?yún)?lái)說(shuō),它允許多種類型的入?yún)ⅲㄟ^(guò)下表進(jìn)行說(shuō)明:

          請(qǐng)求處理方法入?yún)⒌目蛇x類型 說(shuō)明
          Java 基本數(shù)據(jù)類型和 String 默認(rèn)情況下將按名稱匹配的方式綁定到 URL 參數(shù)上,可以通過(guò) @RequestParam 注解改變默認(rèn)的綁定規(guī)則
          request/response/session 既可以是 Servlet API 的也可以是 Portlet API 對(duì)應(yīng)的對(duì)象,Spring 會(huì)將它們綁定到 Servlet 和 Portlet 容器的相應(yīng)對(duì)象上
          org.springframework.web.context.request.WebRequest 內(nèi)部包含了 request 對(duì)象
          java.util.Locale 綁定到 request 對(duì)應(yīng)的 Locale 對(duì)象上
          java.io.InputStream/java.io.Reader 可以借此訪問(wèn) request 的內(nèi)容
          java.io.OutputStream / java.io.Writer 可以借此操作 response 的內(nèi)容
          任何標(biāo)注了 @RequestParam 注解的入?yún)?/td> 被標(biāo)注 @RequestParam 注解的入?yún)⒔壎ǖ教囟ǖ?request 參數(shù)上。
          java.util.Map / org.springframework.ui.ModelMap 它綁定 Spring MVC 框架中每個(gè)請(qǐng)求所創(chuàng)建的潛在的模型對(duì)象,它們可以被 Web 視圖對(duì)象訪問(wèn)(如 JSP)
          命令/表單對(duì)象(注:一般稱綁定使用 HTTP GET 發(fā)送的 URL 參數(shù)的對(duì)象為命令對(duì)象,而稱綁定使用 HTTP POST 發(fā)送的 URL 參數(shù)的對(duì)象為表單對(duì)象) 它們的屬性將以名稱匹配的規(guī)則綁定到 URL 參數(shù)上,同時(shí)完成類型的轉(zhuǎn)換。而類型轉(zhuǎn)換的規(guī)則可以通過(guò) @InitBinder 注解或通過(guò) HandlerAdapter 的配置進(jìn)行調(diào)整
          org.springframework.validation.Errors / org.springframework.validation.BindingResult 為屬性列表中的命令/表單對(duì)象的校驗(yàn)結(jié)果,注意檢驗(yàn)結(jié)果參數(shù)必須緊跟在命令/表單對(duì)象的后面
          rg.springframework.web.bind.support.SessionStatus 可以通過(guò)該類型 status 對(duì)象顯式結(jié)束表單的處理,這相當(dāng)于觸發(fā) session 清除其中的通過(guò) @SessionAttributes 定義的屬性

          Spring MVC 框架的易用之處在于,你可以按任意順序定義請(qǐng)求處理方法的入?yún)ⅲǔ?Errors 和 BindingResult 必須緊跟在命令對(duì)象/表單參數(shù)后面以外),Spring MVC 會(huì)根據(jù)反射機(jī)制自動(dòng)將對(duì)應(yīng)的對(duì)象通過(guò)入?yún)鬟f給請(qǐng)求處理方法。這種機(jī)制讓開(kāi)發(fā)者完全可以不依賴 Servlet API 開(kāi)發(fā)控制層的程序,當(dāng)請(qǐng)求處理方法需要特定的對(duì)象時(shí),僅僅需要在參數(shù)列表中聲明入?yún)⒓纯桑恍枰紤]如何獲取這些對(duì)象,Spring MVC 框架就象一個(gè)大管家一樣“不辭辛苦”地為我們準(zhǔn)備好了所需的一切。下面演示一下使用 SessionStatus 的例子:


          清單 12. 使用 SessionStatus 控制 Session 級(jí)別的模型屬性
                      @RequestMapping(method = RequestMethod.POST)
                      public String processSubmit(@ModelAttribute Owner owner,
                      BindingResult result, SessionStatus status) {//<——①
                      new OwnerValidator().validate(owner, result);
                      if (result.hasErrors()) {
                      return "ownerForm";
                      }
                      else {
                      this.clinic.storeOwner(owner);
                      status.setComplete();//<——②
                      return "redirect:owner.do?ownerId=" + owner.getId();
                      }
                      }
                      

          processSubmit() 方法中的 owner 表單對(duì)象將綁定到 ModelMap 的“owner”屬性中,result 參數(shù)用于存放檢驗(yàn) owner 結(jié)果的對(duì)象,而 status 用于控制表單處理的狀態(tài)。在 ② 處,我們通過(guò)調(diào)用 status.setComplete() 方法,該 Controller 所有放在 session 級(jí)別的模型屬性數(shù)據(jù)將從 session 中清空。

          方法返回參數(shù)

          在低版本的 Spring MVC 中,請(qǐng)求處理方法的返回值類型都必須是 ModelAndView。而在 Spring 2.5 中,你擁有多種靈活的選擇。通過(guò)下表進(jìn)行說(shuō)明:

          請(qǐng)求處理方法入?yún)⒌目蛇x類型 說(shuō)明
          void

          此時(shí)邏輯視圖名由請(qǐng)求處理方法對(duì)應(yīng)的 URL 確定,如以下的方法:

          @RequestMapping("/welcome.do")
                                  public void welcomeHandler() {
                                  }
                                  

          對(duì)應(yīng)的邏輯視圖名為“welcome”

          String

          此時(shí)邏輯視圖名為返回的字符,如以下的方法:

          @RequestMapping(method = RequestMethod.GET)
                                  public String setupForm(@RequestParam("ownerId") int ownerId, ModelMap model) {
                                  Owner owner = this.clinic.loadOwner(ownerId);
                                  model.addAttribute(owner);
                                  return "ownerForm";
                                  }
                                  

          對(duì)應(yīng)的邏輯視圖名為“ownerForm”

          org.springframework.ui.ModelMap

          和返回類型為 void 一樣,邏輯視圖名取決于對(duì)應(yīng)請(qǐng)求的 URL,如下面的例子:

          @RequestMapping("/vets.do")
                                  public ModelMap vetsHandler() {
                                  return new ModelMap(this.clinic.getVets());
                                  }
                                  

          對(duì)應(yīng)的邏輯視圖名為“vets”,返回的 ModelMap 將被作為請(qǐng)求對(duì)應(yīng)的模型對(duì)象,可以在 JSP 視圖頁(yè)面中訪問(wèn)到。

          ModelAndView 當(dāng)然還可以是傳統(tǒng)的 ModelAndView。

          應(yīng)該說(shuō)使用 String 作為請(qǐng)求處理方法的返回值類型是比較通用的方法,這樣返回的邏輯視圖名不會(huì)和請(qǐng)求 URL 綁定,具有很大的靈活性,而模型數(shù)據(jù)又可以通過(guò) ModelMap 控制。當(dāng)然直接使用傳統(tǒng)的 ModelAndView 也不失為一個(gè)好的選擇。





          回頁(yè)首


          注冊(cè)自己的屬性編輯器

          Spring MVC 有一套常用的屬性編輯器,這包括基本數(shù)據(jù)類型及其包裹類的屬性編輯器、String 屬性編輯器、JavaBean 的屬性編輯器等。但有時(shí)我們還需要向 Spring MVC 框架注冊(cè)一些自定義的屬性編輯器,如特定時(shí)間格式的屬性編輯器就是其中一例。

          Spring MVC 允許向整個(gè) Spring 框架注冊(cè)屬性編輯器,它們對(duì)所有 Controller 都有影響。當(dāng)然 Spring MVC 也允許僅向某個(gè) Controller 注冊(cè)屬性編輯器,對(duì)其它的 Controller 沒(méi)有影響。前者可以通過(guò) AnnotationMethodHandlerAdapter 的配置做到,而后者則可以通過(guò) @InitBinder 注解實(shí)現(xiàn)。

          下面先看向整個(gè) Spring MVC 框架注冊(cè)的自定義編輯器:


          清單 13. 注冊(cè)框架級(jí)的自定義屬性編輯器
                      >bean class="org.springframework.web.servlet.mvc.annotation.
                      AnnotationMethodHandlerAdapter"<
                      >property name="webBindingInitializer"<
                      >bean class="com.baobaotao.web.MyBindingInitializer"/<
                      >/property<
                      >/bean<
                      

          MyBindingInitializer 實(shí)現(xiàn)了 WebBindingInitializer 接口,在接口方法中通過(guò) binder 注冊(cè)多個(gè)自定義的屬性編輯器,其代碼如下所示:


          清單 14.自定義屬性編輯器
                      package org.springframework.samples.petclinic.web;
                      import java.text.SimpleDateFormat;
                      import java.util.Date;
                      import org.springframework.beans.factory.annotation.Autowired;
                      import org.springframework.beans.propertyeditors.CustomDateEditor;
                      import org.springframework.beans.propertyeditors.StringTrimmerEditor;
                      import org.springframework.samples.petclinic.Clinic;
                      import org.springframework.samples.petclinic.PetType;
                      import org.springframework.web.bind.WebDataBinder;
                      import org.springframework.web.bind.support.WebBindingInitializer;
                      import org.springframework.web.context.request.WebRequest;
                      public class MyBindingInitializer implements WebBindingInitializer {
                      public void initBinder(WebDataBinder binder, WebRequest request) {
                      SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
                      dateFormat.setLenient(false);
                      binder.registerCustomEditor(Date.class,
                      new CustomDateEditor(dateFormat, false));
                      binder.registerCustomEditor(String.class, new StringTrimmerEditor(false));
                      }
                      }
                      

          如果希望某個(gè)屬性編輯器僅作用于特定的 Controller,可以在 Controller 中定義一個(gè)標(biāo)注 @InitBinder 注解的方法,可以在該方法中向 Controller 了注冊(cè)若干個(gè)屬性編輯器,來(lái)看下面的代碼:


          清單 15. 注冊(cè) Controller 級(jí)的自定義屬性編輯器
                      @Controller
                      public class MyFormController {
                      @InitBinder
                      public void initBinder(WebDataBinder binder) {
                      SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
                      dateFormat.setLenient(false);
                      binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
                      }
                      …
                      }
                      

          注意被標(biāo)注 @InitBinder 注解的方法必須擁有一個(gè) WebDataBinder 類型的入?yún)ⅲ员?Spring MVC 框架將注冊(cè)屬性編輯器的 WebDataBinder 對(duì)象傳遞進(jìn)來(lái)。





          回頁(yè)首


          如何準(zhǔn)備數(shù)據(jù)

          在編寫(xiě) Controller 時(shí),常常需要在真正進(jìn)入請(qǐng)求處理方法前準(zhǔn)備一些數(shù)據(jù),以便請(qǐng)求處理或視圖渲染時(shí)使用。在傳統(tǒng)的 SimpleFormController 里,是通過(guò)復(fù)寫(xiě)其 referenceData() 方法來(lái)準(zhǔn)備引用數(shù)據(jù)的。在 Spring 2.5 時(shí),可以將任何一個(gè)擁有返回值的方法標(biāo)注上 @ModelAttribute,使其返回值將會(huì)進(jìn)入到模型對(duì)象的屬性列表中。來(lái)看下面的例子:


          清單 16. 定義為處理請(qǐng)求準(zhǔn)備數(shù)據(jù)的方法
                      package com.baobaotao.web;
                      import com.baobaotao.service.BbtForumService;
                      import org.springframework.beans.factory.annotation.Autowired;
                      import org.springframework.stereotype.Controller;
                      import org.springframework.ui.ModelMap;
                      import org.springframework.web.bind.annotation.ModelAttribute;
                      import org.springframework.web.bind.annotation.RequestMapping;
                      import org.springframework.web.bind.annotation.RequestParam;
                      import org.springframework.web.bind.annotation.SessionAttributes;
                      import java.util.ArrayList;
                      import java.util.List;
                      import java.util.Set;
                      @Controller
                      @RequestMapping("/bbtForum.do")
                      public class BbtForumController {
                      @Autowired
                      private BbtForumService bbtForumService;
                      @ModelAttribute("items")//<——①向模型對(duì)象中添加一個(gè)名為items的屬性
                      public List<String> populateItems() {
                      List<String> lists = new ArrayList<String>();
                      lists.add("item1");
                      lists.add("item2");
                      return lists;
                      }
                      @RequestMapping(params = "method=listAllBoard")
                      public String listAllBoard(@ModelAttribute("currUser")User user, ModelMap model) {
                      bbtForumService.getAllBoard();
                      //<——②在此訪問(wèn)模型中的items屬性
                      System.out.println("model.items:" + ((List<String>)model.get("items")).size());
                      return "listBoard";
                      }
                      }
                      

          在 ① 處,通過(guò)使用 @ModelAttribute 注解,populateItem() 方法將在任何請(qǐng)求處理方法執(zhí)行前調(diào)用,Spring MVC 會(huì)將該方法返回值以“items”為名放入到隱含的模型對(duì)象屬性列表中。

          所以在 ② 處,我們就可以通過(guò) ModelMap 入?yún)⒃L問(wèn)到 items 屬性,當(dāng)執(zhí)行 listAllBoard() 請(qǐng)求處理方法時(shí),② 處將在控制臺(tái)打印出“model.items:2”的信息。當(dāng)然我們也可以在請(qǐng)求的視圖中訪問(wèn)到模型對(duì)象中的 items 屬性。





          回頁(yè)首


          小結(jié)

          Spring 2.5 對(duì) Spring MVC 進(jìn)行了很大增強(qiáng),現(xiàn)在我們幾乎完全可以使用基于注解的 Spring MVC 完全替換掉原來(lái)基于接口 Spring MVC 程序。基于注解的 Spring MVC 比之于基于接口的 Spring MVC 擁有以下幾點(diǎn)好處:

          • 方便請(qǐng)求和控制器的映射;
          • 方便請(qǐng)求處理方法入?yún)⒔壎║RL參數(shù);
          • Controller 不必繼承任何接口,它僅是一個(gè)簡(jiǎn)單的 POJO。

          但是基于注解的 Spring MVC 并不完美,還存在優(yōu)化的空間,因?yàn)樵谀承┡渲蒙纤然?XML 的配置更繁瑣。比如對(duì)于處理多個(gè)請(qǐng)求的 Controller 來(lái)說(shuō),假設(shè)我們使用一個(gè) URL 參數(shù)指定調(diào)用的處理方法(如 xxx.do?method=listBoardTopic),當(dāng)使用注解時(shí),每個(gè)請(qǐng)求處理方法都必須使用 @RequestMapping() 注解指定對(duì)應(yīng)的 URL 參數(shù)(如 @RequestMapping(params = "method=listBoardTopic")),而在 XML 配置中我們僅需要配置一個(gè) ParameterMethodNameResolver 就可以了。


          只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 新蔡县| 于田县| 无棣县| 资兴市| 永年县| 通河县| 枝江市| 巫溪县| 湛江市| 临洮县| 彰化市| 紫阳县| 丰顺县| 无为县| 玉田县| 和田县| 阆中市| 宁津县| 潜山县| 时尚| 道孚县| 文成县| 平山县| 商河县| 荣昌县| 怀来县| 天台县| 合川市| 巩留县| 高平市| 克山县| 江山市| 太湖县| 郎溪县| 嵊泗县| 内丘县| 固安县| 汉阴县| 新田县| 枣阳市| 林西县|