第三章:註解式控制器開發詳解 ——深入淺出學Spring Web MVC

註解式控制器開發簡介

Spring2.5之前,我們都是通過實現Controller接口或其實現來定義我們的處理器類。已經@Deprecated,建議不再使用了
Spring2.5引入註解式處理器支持,通過@Controller 和 @RequestMapping註解定義我們的處理器類。並且提供了一組強大的註解:
@Controller:用於標識是處理器類;
@RequestMapping:請求到處理器功能方法的映射規則;
@RequestParam:請求參數到處理器功能處理方法的方法參數上的綁定;
@ModelAttribute:請求參數到命令對象的綁定;
@SessionAttributes:用於聲明session級別存儲的屬性,放置在處理器類上,通常列出模型屬性(如@ModelAttribute)對應的名稱,則這些屬性會透明的保存到session中;
@InitBinder:自定義數據綁定註冊支持,用於將請求參數轉換到命令對象屬性的對應類型;
 
注意:需要通過處理器映射DefaultAnnotationHandlerMapping和處理器適配器AnnotationMethodHandlerAdapter來開啓支持@Controller 和 @RequestMapping註解的處理器。
Spring3.0引入RESTful架構風格支持(通過@PathVariable註解和一些其他特性支持),且又引入了更多的註解支持
@CookieValue:cookie數據到處理器功能處理方法的方法參數上的綁定;
@RequestHeader:請求頭(header)數據到處理器功能處理方法的方法參數上的綁定;
@RequestBody:請求的body體的綁定(通過HttpMessageConverter進行類型轉換);
@ResponseBody:處理器功能處理方法的返回值作爲響應體(通過HttpMessageConverter進行類型轉換);
@ResponseStatus:定義處理器功能處理方法/異常處理器返回的狀態碼和原因;
@ExceptionHandler:註解式聲明異常處理器;
@PathVariable:請求URI中的模板變量部分到處理器功能處理方法的方法參數上的綁定,從而支持RESTful架構風格的URI;
Spring3.1使用新的HandlerMapping 和 HandlerAdapter來支持@Contoller和@RequestMapping註解處理器 ,使用處理器映射RequestMappingHandlerMapping 和 處理器適配器RequestMappingHandlerAdapter組合來代替Spring2.5開始的處理器映射DefaultAnnotationHandlerMapping和處理器適配器AnnotationMethodHandlerAdapter。
 

註解式控制器開發HelloWorld

實現控制器

java代碼:
  1. @Controller   // 或 @RequestMapping   //①將一個POJO類聲明爲處理器  
  2. public class HelloWorldController {  
  3.     @RequestMapping(value="/hello")//②請求URL到處理器功能處理方法的映射  
  4.     public ModelAndView helloWorld() {  
  5.         //1、收集參數   //2、綁定參數到命令對象  
  6.         //3、調用業務對象  //4、選擇下一個頁面  
  7.         ModelAndView mv = new ModelAndView();  
  8.         //添加模型數據 可以是任意的POJO對象  
  9.         mv.addObject("message""Hello World!");  
  10.         //設置邏輯視圖名,視圖解析器會根據該名字解析到具體的視圖頁面  
  11.         mv.setViewName("hello");  
  12.         return mv;                //3 模型數據和邏輯視圖名  
  13.     }  
  14. }  

1:可以通過在一個POJO類上放置@Controller或@RequestMapping,即可把一個POJO類變身爲處理器;
2:@RequestMapping(value="/hello") 請求URL到 處理器的功能處理方法的映射;
3:現在的處理器無需實現/繼承任何接口/類,只需要在相應的類/方法上放置相應的註解說明下即可,非常方便。
 
HandlerMapping和HandlerAdapter的配置
如果您使用的是Spring3.1之前版本,開啓註解式處理器支持的配置爲:DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter。
<bean
class= "org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"/>
<bean
class= "org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"/>
如果您使用的Spring3.1開始的版本,建議使用RequestMappingHandlerMapping和RequestMappingHandlerAdapter。
<bean
class= "org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapp ing"/>
<bean
class= "org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdap ter"/>
 
視圖解析器的配置
還是使用之前的org.springframework.web.servlet.view.InternalResourceViewResolver
處理器的配置
<bean name= "/hello"class= "cn.javass.springmvc.hello.HelloWorldController"/>
視圖頁面(/WEB-INF/jsp/hello.jsp)
還是前面演示的頁面,這裏就不重複了
 

HelloWorld的運行流程

 
和前面第一章的HelloWorld不同之處在於:
1、HandlerMapping實現:使用DefaultAnnotationHandlerMapping(spring3.1之前)或RequestMappingHandlerMapping(spring3.1)替換之前的BeanNameUrlHandlerMapping。
 
註解式處理器映射會掃描spring容器中的bean,發現bean實現類上擁有@Controller或@RequestMapping註解的bean,並將它們作爲處理器。
 
2、HandlerAdapter實現:使用AnnotationMethodHandlerAdapter(spring3.1之前)或RequestMappingHandlerAdapter(spring3.1)替換之前的SimpleControllerHandlerAdapter。
 
註解式處理器適配器會通過反射調用相應的功能處理方法(方法上擁有@RequestMapping註解)。
 

處理器定義


java代碼:
  1. @Controller  
  2. public class HelloWorldController {  
  3. ……  
  4. }  

推薦使用這種方式聲明處理器,它和@Service、@Repository很好的對應了我們常見的三層開發架構的組件。
 

java代碼:
  1. @RequestMapping  
  2. public class HelloWorldController {  
  3. ……  
  4. }  

這種方式也是可以工作的,但如果在類上使用@ RequestMapping註解一般是用於窄化功能處理方法的映射的
 
窄化請求映射

java代碼:
  1. @Controller  
  2. @RequestMapping(value="/user")              //①處理器的通用映射前綴  
  3. public class HelloWorldController2 {  
  4.     @RequestMapping(value = "/hello2")      //②相對於①處的映射進行窄化  
  5.     public ModelAndView helloWorld() {  
  6.          //省略實現  
  7.     }  
  8. }  

所謂窄化,就是在前面路徑的基礎上,繼續細化的意思。比如:
http://localhost:9080/mvcexample/hello2 無法映射到HelloWorldController2的 helloWorld功能處理方法;
而http://localhost:9080/mvcexample /user/hello2是可以的
 

REST簡介

REST(Representational State Transfer,簡稱REST )是什麼
REST(表徵狀態轉移)是Roy Fielding博士在2000年他的博士論文中提出來的一種軟件架構風格。
REST 從資源的角度來觀察整個網絡,分佈在各處的資源由URI確定,而客戶端的應用通過URI來獲取資源的表徵。獲得這些表徵致使這些應用程序轉變了其狀態。隨着不斷獲取資源的表徵,客戶端應用不斷地在轉變着其狀態,所謂表徵狀態轉移。
如果一個架構符合REST原則,就稱它爲RESTful架構。
REST特點
1:REST是設計風格而不是標準。REST通常基於使用HTTP,URI,和XML以及HTML這些現有的廣泛流行的協議和標準。
2:資源是由URI來指定
3:對資源的操作包括獲取、創建、修改和刪除資源
4:通過操作資源的表現形式來操作資源
 
示例
展示私塾在線的熱銷課程:  http://sishuok.com/category/1
展示某一門具體的課程: http://sishuok.com/product/681
REST的優點
1:可以利用緩存Cache來提高響應速度
2:通訊本身的無狀態性可以讓不同的服務器的處理一系列請求中的不同請求,提高服務器的擴展性
3:瀏覽器即可作爲客戶端,簡化軟件需求
4:相對於其他疊加在HTTP協議之上的機制,REST的軟件依賴性更小
5:不需要額外的資源發現機制
6:在軟件技術演進中的長期的兼容性更好

請求映射 

先來看看HttpRequest的格式
 
nHttpRequest信息包含六部分:
①請求方法,如GET或POST,表示提交的方式;
②URL,請求的地址信息;
③協議及版本;
④請求頭信息(包括Cookie信息);
⑤回車換行;
⑥請求內容區(即請求的內容或數據),如表單提交時的參數數據、URL請求參數(?abc=123 ?後邊的)等。
可以看到有①、②、④、⑥一般是可變的,因此我們可以把這些信息進行請求到處理器的功能處理方法的映射分爲如下幾種:
1:URL路徑映射:使用URL映射請求到處理器的功能處理方法;
2:請求方法映射限定:如限定功能處理方法只處理GET請求;
3:請求參數映射限定:如限定只處理包含“abc”請求參數的請求;
4:請求頭映射限定:如限定只處理“Accept=application/json”的請求。
 

URL路徑映射

n普通URL路徑映射
@RequestMapping(value={"/test1", "/user/create"}):多個URL路徑可以映射到同一個處理器的功能處理方法。
nURI模板模式映射
1:@RequestMapping(value="/users/{userId}"):{×××}佔位符, 請求的URL可以是 “/users/123456”或
“/users/abcd”,通過後面講的通過@PathVariable可以提取URI模板模式中的{×××}中的×××變量。
 
2:@RequestMapping(value=“/users/{userId}/create”):這樣也是可以的,請求的URL可以是“/users/123/create”。
 
3:@RequestMapping(value="/users/{userId}/topics/{topicId}"):這樣也是可以的,請求的URL可以是“/users/123/topics/123”。
 
Ant風格的URL路徑映射
1:@RequestMapping(value=“/users/**”):可以匹配“/users/abc/abc”,但“/users/123”將會被【URI模板模式映射中的“/users/{userId}”模式優先映射到】。 因爲最長匹配優 先
2:@RequestMapping(value="/product?"):可匹配“/product1”或“/producta”,但不匹配“/product”或“/productaa”;
3:@RequestMapping(value="/product*"):可匹配“/productabc”或“/product”,但不匹配“/productabc/abc”;
4:@RequestMapping(value="/product/*"):可匹配“/product/abc”,但不匹配“/productabc”;
5:@RequestMapping(value="/products/**/{productId}"):可匹配“/products/abc/abc/123”或“/products/123”,也就是Ant風格和URI模板變量風格可混用;
 
Ant-style 模式
?  匹配一個字符,如/index?可以匹配/index1,但不能匹配/index或/index12
*  匹配零個或多個字符,如/index1/*,可以匹配/index1/demo,但不匹配/index1/demo/demo
** 匹配零個或多個路徑,如/index2/**:可以匹配/index2路徑下的所有子路徑,如匹配/index2/demo,或/index2/demo/demo
 
如果有如下模式,那Spring該選擇哪一個執行呢?當請求爲“/long/long”時如下所示:
/long/long
/long/**/abc
/long/**
/**
Spring的AbstractUrlHandlerMapping使用:最長匹配優先;
如請求爲“/long/long” 將匹配第一個“/long/long”,但請求“/long/acd” 則將匹配 “/long/**”,如請求“/long/aa/abc”則匹配“/long/**/abc”,如請求“/abc”則將匹配“/**”
 
正則表達式風格的URL路徑映射
從Spring3.0開始支持正則表達式風格的URL路徑映射,格式爲{變量名:正則表達式},這樣就可以通過@PathVariable提取模式中的{×××:正則表達式匹配的值}中的×××變量了。
比如:@RequestMapping(value=“/products/{categoryCode:\\d+}-{pageNumber:\\d+}”):可以匹配“/products/123-1”,但不能匹配“/products/abc-1”,這樣可以設計更加嚴格的規則。
正則表達式風格的URL路徑映射是一種特殊的URI模板模式映射:URI模板模式映射是{userId},不能指定模板變量的數據類型,如是數字還是字符串;正則表達式風格的URL路徑映射,可以指定模板變量的數據類型,可以將規則寫的相當複雜。
組合使用是“或”的關係
如 @RequestMapping(value={"/test1", "/user/create"}) 組合使用是或的關係,即“/test1”或“/user/create”請求URL路徑都可以映射到@RequestMapping指定的功能處理方法。
 
請求方法映射限定示例
@Controller
@RequestMapping("/customers/**")   //①處理器的通用映射前綴
public class RequestMethodController {
   @RequestMapping(value="/create", method = RequestMethod. GET)//②類級別的@RequestMapping窄化
    public String showForm() {
        System. out.println("===============GET");
        return "customer/create"; 
    }
  @RequestMapping(value="/create", method = RequestMethod. POST)//③類級別的@RequestMapping窄化
    public String submit() {
        System. out.println("================POST");
        return "redirect:/success";       
    }
}
①處理器的通用映射前綴(父路徑):表示該處理器只處理匹配“/customers/**”的請求;
②對類級別的@RequestMapping進行窄化,表示showForm可處理匹配“/customers/**/create”且請求方法爲“GET”的請求;
③對類級別的@RequestMapping進行窄化,表示submit可處理匹配“/customers/**/create”且請求方法爲“POST”的請求。
 
組合使用是“或”的關係
@RequestMapping(value="/methodOr", method = {RequestMethod. POST, RequestMethod. GET}):即請求方法可以是 GET 或 POST。
 
提示:
1、一般瀏覽器只支持GET、POST請求方法,如想瀏覽器支持PUT、DELETE等請求方法只能模擬。
2、除了GET、POST,還有HEAD、OPTIONS、PUT、DELETE、TRACE。
3、DispatcherServlet默認開啓對 GET、POST、PUT、DELETE、HEAD的支持;
4、如果需要支持OPTIONS、TRACE,請添加DispatcherServlet在web.xml的初始化參數:dispatchOptionsRequest 和 dispatchTraceRequest 爲true。
 
 

請求參數映射

請求數據中有指定參數名的示例
@Controller
@RequestMapping("/parameter1")                //①處理器的通用映射前綴
public class RequestParameterController1 {
    //②進行類級別的@RequestMapping窄化
    @RequestMapping(params="create", method=RequestMethod.GET)
    public String showForm() {
        System.out.println("===============showForm");
        return "parameter/create";       
    }
    //③進行類級別的@RequestMapping窄化
    @RequestMapping(params="create", method=RequestMethod.POST) 
    public String submit() {
        System.out.println("================submit");
        return "redirect:/success";       
    }
}
 
示例說明
1:②@RequestMapping(params=“create”, method=RequestMethod. GET) :表示請求中有“create”的參數名且請求方法爲“GET”即可匹配,如可匹配的請求URL“http://×××/parameter1?create”;
2:③@RequestMapping(params=“create”, method=RequestMethod. POST):表示請求中有“create”的參數名且請求方法爲“POST”即可匹配;
3:此處的create請求參數名錶示你請求的動作,即你想要的功能的一個標識,常見的CRUD(增刪改查)我們可以使用如下請求參數名來表達:
(create請求參數名 且 GET請求)新增頁面展示、(create請求參數名 且 POST請求)新增提交
(update請求參數名 且 GET請求)修改頁面展示、(update請求參數名 且 POST請求)修改提交;
(delete請求參數名 且 GET請求)刪除頁面展示、(delete請求參數名 且 POST請求)刪除提交;
(query請求參數名 且 GET請求)查詢頁面展示、(query請求參數名 且 POST請求)查詢提交;
(list請求參數名 且 GET請求)列表頁面展示;
(view請求參數名 且 GET請求)查看單條記錄頁面展示。
 
請求數據中沒有指定參數名的示例
例子:@RequestMapping(params=“!create”, method=RequestMethod. GET)
1:@RequestMapping(params="!create", method=RequestMethod. GET):表示請求中沒有 “create”參數名且請求方法爲“GET”即可匹配,如可匹配的請求URL“http://×××/parameter1?abc”
n請求數據中指定參數名=值 的示例
例子:@RequestMapping(params="submitFlag=create") 
1:上例表示請求中有“submitFlag=create”請求參數即可匹配,如請求URL爲 http://×××/parameter2?submitFlag=create
n請求數據中指定參數名!=值 的示例
例子:@RequestMapping(params="submitFlag!=create", method=RequestMethod. GET
1:上例表示請求中的參數“submitFlag!=create”且請求方法爲“GET”即可匹配,如可匹配的請求URL“http://×××/parameter1?submitFlag=abc”。
組合使用是“且”的關係
例子:@RequestMapping(params={“test1”, “test2=create”})
1:@RequestMapping(params={"test1", "test2=create"}):表示請求中的有“test1”參數名 且 有 “test2=create”參數即可匹配,如可匹配的請求URL“http://×××/parameter3?test1&test2=create。

請求頭數據映射 

準備環境
瀏覽器:建議chrome最新版本;
插件安裝步驟:
 
 
 
2、點擊“添加至chrome”後彈出“確認安裝”對話框,點擊“安裝”按鈕即可,如圖
  
3、安裝成功後,在瀏覽器右上角出現如圖的圖標表示安裝成功:
  
4、鼠標右擊右上角的“Modify Header”圖標,選擇選項,打開如圖
 
5:修改完成後,輸入URL請求,你可以在chrome的“開發人員工具的”網絡選項卡下,看到如圖的信息表示添加請求頭成功了:
  
 
請求頭數據中有指定參數名
例子1:@RequestMapping(value=“/header/test1”, headers = “Accept”):
表示請求的URL必須爲“/header/test1”且 請求頭中必須有Accept參數才能匹配。
 
例子2:@RequestMapping(value=“/header/test1”, headers = “abc”):表示請求的URL必須爲“/header/test1”且 請求頭中必須有abc參數才能匹配,
 
請求頭數據中沒有指定參數名
例子:RequestMapping(value=“/header/test2”, headers = “!abc”)
表示請求的URL必須爲“/header/test2” 且 請求頭中必須沒有abc參數才能匹配。(將Modify Header的abc參數值刪除即可)
 
請求頭數據中指定參數名=值
例子1:@RequestMapping(value="/header/test3", headers = " Content- Type=application/json")
表示請求的URL必須爲“/header/test3” 且 請求頭中必須有“Content-Type=application/json”參數即可匹配。(將Modify Header的Content-Type參數值改爲“application/json”即可)
 
說明:當請求的URL爲“/header/test3” 但 如果請求頭中沒有或不是“Content-Type=application/json”參數(如“text/html”其他參數),將返回“HTTP Status 415”狀態碼【表示不支持的媒體類型(Media Type),也就是MIME類型】,即我們的功能處理方法只能處理application/json 的媒體類型。
 
例子2:@RequestMapping(value=“/header/test4”, headers = “ Accept=application/json”):表示請求的URL必須爲“/header/test4” 且 請求頭中必須有“Accept =application/json”參數即可匹配。(將Modify Header的Accept參數值改爲“application/json”即可);
說明:當你請求的URL爲“/header/test4” 但 如果請求頭中沒有“Accept=application/json”參數(如“text/html”其他參數),將返回“HTTP Status 406”狀態碼【不可接受,服務器無法根據Accept頭的媒體類型爲客戶端生成響應】,即客戶只接受“application/json”媒體類型的數據,即我們的功能處理方法的響應只能返回“application/json”媒體類型的數據。
 
例子3:@RequestMapping(value=“/header/test5”, headers = “Accept=text/*”) :表示請求的URL必須爲“/header/test5” 且 請求頭中必須有如“Accept=text/plain”參數即可匹配。(將Modify Header的Accept參數值改爲“text/plain”即可);
說明:Accept=text/*:表示主類型爲text,子類型任意,如“text/plain”、“text/html”等都可以匹配。
 
例子4:@RequestMapping(value=“/header/test6”, headers = “Accept=*/*”) :表示請求的URL必須爲“/header/test6” 且 請求頭中必須有任意Accept參數即可匹配。(將Modify Header的Accept參數值改爲“text/html”或“application/xml”等都可以)
說明:Accept=*/*:表示主類型任意,子類型任意,如“text/plain”、“application/xml”等都可以匹配。
 
請求頭數據中指定參數名!=值
@RequestMapping(value=“/header/test7”, headers = “Accept!=text/vnd.wap.wml”):表示請求的URL必須爲“/header/test7” 且 請求頭中必須有“Accept”參數但值不等於“text/vnd.wap.wml”即可匹配。
組合使用是“且”的關係
@RequestMapping(value=“/header/test8”, headers = {“Accept!=text/vnd.wap.wml”, “abc=123”}):表示請求的URL必須爲“/header/test8” 且 請求頭中必須有“Accept”參數但值不等於“text/vnd.wap.wml”且 請求中必須有參數“abc=123”即可匹配。

數據綁定

到目前爲止,請求已經能交給處理器進行處理了,接下來的事情是要進行收集數據啦,接下來我們看看能從請求中收集到哪些數據,如圖
說明
1、@RequestParam綁定單個請求參數值;
2、@PathVariable綁定URI模板變量值;
3、@CookieValue綁定Cookie數據值
4、@RequestHeader綁定請求頭數據;
5、@ModelValue綁定參數到命令對象;
6、@SessionAttributes綁定命令對象到session;
7、@RequestBody綁定請求的內容區數據並能進行自動類型轉換等。
8、@RequestPart綁定“multipart/data”數據,除了能綁定@RequestParam能做到的請求參數外,還能綁定上傳的文件等。
 
除了上邊提到的註解,還可以通過如HttpServletRequest等API得到請求數據,但推薦使用註解方式,因爲使用起來更簡單
 
 
功能處理方法支持的參數類型 ,以及他們的具體含義
一、ServletRequest/HttpServletRequest和 ServletResponse/HttpServletResponse
public String requestOrResponse (
        ServletRequest servletRequest, HttpServletRequest httpServletRequest,
        ServletResponse servletResponse, HttpServletResponse httpServletResponse) 
Spring Web MVC框架會自動把相應的Servlet請求/響應作爲參數傳遞過來。
 
二、InputStream/OutputStream 和 Reader/Writer
public void inputOrOutBody(InputStream requestBodyIn, OutputStream responseBodyOut)
        throws IOException {responseBodyOut.write("success".getBytes());   } 
requestBodyIn:獲取請求的內容區字節流,等價於request.getInputStream();
responseBodyOut:獲取相應的內容區字節流,等價於response.getOutputStream()。
 
public void readerOrWriteBody(Reader reader, Writer writer)
        throws IOException {   writer.write("hello");  } 
reader:獲取請求的內容區字符流,等價於request.getReader();
writer:獲取相應的內容區字符流,等價於response.getWriter()。
 
注意:InputStream/OutputStream 和 Reader/Writer兩組不能同時使用,只能使用其中的一組。
三、WebRequest/NativeWebRequest
WebRequest是Spring Web MVC提供的統一請求訪問接口,不僅僅可以訪問請求相關數據(如參數區數據、請求頭數據,但訪問不到Cookie區數據),還可以訪問會話和上下文中的數據;NativeWebRequest繼承了WebRequest,並提供訪問本地Servlet API的方法。
public String webRequest(WebRequest webRequest, NativeWebRequest nativeWebRequest) {
    System. out.println(webRequest.getParameter("test"));//①得到請求參數test的值
    webRequest.setAttribute("name", "value", WebRequest. SCOPE_REQUEST);//②
    System. out.println(webRequest.getAttribute("name", WebRequest. SCOPE_REQUEST));
    HttpServletRequest request =
        nativeWebRequest.getNativeRequest(HttpServletRequest.class);//③
    HttpServletResponse response =
        nativeWebRequest.getNativeResponse(HttpServletResponse.class);
        return "success";
    }
① webRequest.getParameter:訪問請求參數區的數據,可以通過getHeader()訪問請求頭數據;
② webRequest.setAttribute/getAttribute:到指定的作用範圍內取/放屬性數據,Servlet定義的三個作用範圍分別使用如下常量代表:
             SCOPE_REQUEST :代表請求作用範圍;
             SCOPE_SESSION :代表會話作用範圍;
             SCOPE_GLOBAL_SESSION :代表全局會話作用範圍,即ServletContext上下文作用範圍。 
③ nativeWebRequest.getNativeRequest/nativeWebRequest.getNativeResponse:得到本地ServletAPI
 
四、 HttpSession
public String session(HttpSession session) {
    System. out.println(session);
    return "success";

此處的session永遠不爲null。
注意:session訪問不是線程安全的,如果需要線程安全,需要設置AnnotationMethodHandlerAdapter或RequestMappingHandlerAdapter的synchronizeOnSession屬性爲true,即可線程安全的訪問session。
 
五、命令/表單對象
Spring Web MVC能夠自動將請求參數綁定到功能處理方法的命令/表單對象上。
@RequestMapping(value = "/commandObject", method = RequestMethod. GET)
public String toCreateUser(HttpServletRequest request, UserModel user) {
    return "customer/create";
}
@RequestMapping(value = "/commandObject", method = RequestMethod. POST)
public String createUser(HttpServletRequest request, UserModel user) {
    System. out.println(user);
    return "success";

如果提交的表單(包含username和password文本域),將自動將請求參數綁定到命令對象user中去。
 
六、 Model、Map、ModelMap
Spring Web MVC 提供Model、Map或ModelMap讓我們能去暴露渲染視圖需要的模型數據。
public String createUser(Model model, Map model2, ModelMap model3) {
    model.addAttribute("a", "a");
    model2.put("b", "b");
    model3.put("c", "c");
    System. out.println(model == model2);
    System. out.println(model2 == model3);
    return "success";}
                           
注意:雖然此處注入的是三個不同的類型(Model model,Map model2, ModelMap model3),但三者是同一個對象
  
AnnotationMethodHandlerAdapter和RequestMappingHandlerAdapter將使用
BindingAwareModelMap作爲模型對象的實現,即此處我們的形參(Model model, Map model2, ModelMap model3)都是同一個BindingAwareModelMap實例。
 
此處還有一點需要我們注意:
public ModelAndView mergeModel(Model model) {
    model.addAttribute("a", "a");//①添加模型數據
    ModelAndView mv = new ModelAndView("success");
    mv.addObject("a", "update");//②在視圖渲染之前更新③處同名模型數據
    model.addAttribute("a", "new");//③修改①處同名模型數據
    //視圖頁面的a將顯示爲"update" 而不是"new"
    return mv;
}

從代碼中我們可以總結出功能處理方法的返回值中的模型數據(如ModelAndView)會 合併 功能處理方法形式參數中的模型數據(如Model),但如果兩者之間有同名的,返回值中的模型數據會覆蓋形式參數中的模型數據。
 
 
七、Errors/BindingResult
public String error1(UserModel user, BindingResult result)
 
public String error2(UserModel user, BindingResult result, Model model)
 
public String error3(UserModel user, Errors errors)
以上代碼都能獲取錯誤對象。
 
Spring3.1之前(使用AnnotationMethodHandlerAdapter)錯誤對象必須緊跟在命令對象/表單對象之後,如下定義是錯誤的:
 
public String error4(UserModel user, Model model, Errors errors)
 
如上代碼從Spring3.1開始(使用RequestMappingHandlerAdapter)將能正常工作,但還是推薦“錯誤對象緊跟在命令對象/表單對象之後”,這樣是萬無一失的。
 
八、其他雜項
public String other(Locale locale, Principal principal)
 
java.util.Locale:得到當前請求的本地化信息,默認等價於ServletRequest.getLocale(),如果配置LocaleResolver解析器則由它決定Locale,後續介紹;
 
java.security.Principal:該主體對象包含了驗證通過的用戶信息,等價於HttpServletRequest.getUserPrincipal()。
 
 
還有其他功能處理方法的形式參數類型(如HttpEntity、UriComponentsBuilder、SessionStatus、RedirectAttributes)等等。
 
n@RequestParam :用於將請求參數區數據映射到功能處理方法的參數上。
例子:public String requestparam1(@RequestParam String username)
1:如果請求中包含username參數(如/requestparam1?username=zhang),則自動傳入。
2:也可以使用@RequestParam("username")明確告訴Spring Web MVC使用username進行入參
n@RequestParam的主要參數
value:參數名字,即入參的請求參數名字,如username表示請求的參數區中的名字爲username的參數的值將傳入;
required:是否必須,默認是true,表示請求中一定要有相應的參數,否則將報400錯誤碼;
defaultValue:默認值,表示如果請求中沒有同名參數時的默認值,默認值可以是SpEL表達式,如“#{systemProperties[‘java.vm.version’]}”。
例子1:public String test(@RequestParam(value="username",required=false) String username)
表示請求中可以沒有名字爲username的參數,如果沒有默認爲null,需注意如下幾點:
(1):原子類型:必須有值,否則拋出異常,如果允許空值請使用包裝類代替。
(2):Boolean包裝類型類型:默認Boolean.FALSE,其他引用類型默認爲null。
 
例子2:public String requestparam5(
@RequestParam(value="username", required=true, defaultValue="zhang") String username) 表示如果請求中沒有名字爲username的參數,默認值爲“zhang”。
n如果請求中有多個同名的應該如何接收呢?如給用戶授權時,可能授予多個權限,先看如下代碼:
public String test(@RequestParam(value="role") String roleList)
如果請求參數類似於url?role=admin&role=user,則實際roleList參數入參的數據爲“admin,user”,即多個數據之間使用“,”分割;我們應該使用如下方式來接收多個請求參數:
public String test(@RequestParam(value="role") String[] roleList)
public String test(@RequestParam(value="list") List<String> list)
 
n@PathVariable
功能:用於將請求URL中的模板變量映射到功能處理方法的參數上
例子:
@RequestMapping(value="/users/{userId}/topics/{topicId}")
public String test(
       @PathVariable(value="userId") int userId,
       @PathVariable(value="topicId") int topicId)
 
如請求的URL爲“控制器URL/users/123/topics/456”,則自動將URL中模板變量{userId}和{topicId}綁定到通過@PathVariable註解的同名參數上,即入參後userId=123、topicId=456
 
n@CookieValue
功能:用於將請求的Cookie數據映射到功能處理方法的參數上
例子1:
public String test(@CookieValue(value="JSESSIONID", defaultValue="") String sessionId)
 
如上配置將自動將JSESSIONID值入參到sessionId參數上,defaultValue表示Cookie中沒有JSESSIONID時默認爲空。
例子2:
public String test2(@CookieValue(value="JSESSIONID", defaultValue="") Cookie sessionId)
 
傳入參數類型也可以是javax.servlet.http.Cookie類型。
 
@CookieValue也擁有和@RequestParam相同的三個參數,含義一樣。
 
n@RequestHeader
功能:用於將請求的頭信息區數據映射到功能處理方法的參數上
例子:
@RequestMapping(value="/header")
public String test(
       @RequestHeader("User-Agent") String userAgent,
       @RequestHeader(value="Accept") String[] accepts)
      

如上配置將自動將請求頭“User-Agent”值入參到userAgent參數上,並將“Accept”請求頭值入參到accepts參數上。
 
@RequestHeader也擁有和@RequestParam相同的三個參數,含義一樣。
 
n@ModelAttribute
@ModelAttribute一般具有如下三個作用:
1:綁定請求參數到命令對象:放在功能處理方法的入參上時,用於將多個請求參數綁定到一個命令對象,從而簡化綁定流程,而且自動暴露爲模型數據用於視圖頁面展示時使用;
2:暴露表單引用對象爲模型數據:放在處理器的一般方法(非功能處理方法)上時,是爲表單準備要展示的表單引用對象,如註冊時需要選擇的所在城市等,而且在執行功能處理方法(@RequestMapping註解的方法)之前,自動添加到模型對象中,用於視圖頁面展示時使用;
3:暴露@RequestMapping方法返回值爲模型數據:放在功能處理方法的返回值上時,是暴露功能處理方法的返回值爲模型數據,用於視圖頁面展示時使用。
 
一、綁定請求參數到命令對象
如實現用戶登錄,需要捕獲用戶s登錄的請求參數(用戶名、密碼)並封裝爲用戶對象,此時可以使用@ModelAttribute綁定多個請求參數到我們的命令對象。
例子:public String test1(@ModelAttribute("user") UserModel user)
說明:1:和前面命令/表單對象一樣,只是此處多了一個註解@ModelAttribute(“user”),它的作用是將該綁定的命令對象以“user”爲名稱添加到模型對象中供視圖頁面展示使用。我們此時可以在視圖頁面使用${user.username}來獲取綁定的命令對象的屬性。
2:綁定請求參數到命令對象支持對象圖導航式的綁定,如請求參數包含“?username=zhang&password=123&workInfo.city=bj”自動綁定到user中的workInfo屬性的city屬性中。
3:@RequestMapping(value="/model2/{username}")
public String test2(@ModelAttribute("model") UserModel model) {
URI模板變量也能自動綁定到命令對象中,當你請求的URL中包含“&username=zhang”會自動綁定到命令對象上。
當URI模板變量和請求參數同名時, 請求參數 具有高優先權。
 
二、暴露表單引用對象爲模型數據
例子1:
@ModelAttribute("cityList")
public List<String> cityList() {
    return Arrays. asList("北京", "山東");
}
 
如上代碼會在執行功能處理方法之前執行,並將其自動添加到模型對象中,在功能處理方法中可以使用Model入參,則可以在處理方法中使用citylist了,如:
public ModelAndView handleRequest(Model m) {
List<String> list = (List<String>)m.asMap().get("cityList");
for(String s : list){
System.out.println("s==="+s);
}
......
}
 
例子2:
@ModelAttribute("user")  //①
public UserModel getUser(@RequestParam(value=“username", defaultValue="") String username) {  //TODO 去數據庫根據用戶名查找用戶對象  } 
如你要修改用戶資料時一般需要根據用戶的編號/用戶名查找用戶來進行編輯,此時可以通過如上代碼查找要編輯的用戶。也可以進行一些默認值的處理。
@RequestMapping(value="/model1") //②
public String test1(@ModelAttribute("user") UserModel user, Model model)
說明: 
此處我們看到①和②有同名的命令對象,那Spring Web MVC內部如何處理的呢:
1、首先執行@ModelAttribute註解的方法,準備視圖展示時所需要的模型數據;@ModelAttribute註解方法形式參數規則和@RequestMapping規則一樣,如可以有@RequestParam等;
2、執行@RequestMapping註解方法,進行模型綁定時首先查找模型數據中是否含有同名對象,如果有直接使用,如果沒有通過反射創建一個,因此②處的user將使用①處返回的命令對象。即②處的user等於①處的user。
 
三、暴露@RequestMapping方法返回值爲模型數據
例子:
public @ModelAttribute("user2") UserModel test3(@ModelAttribute("user2") UserModel user)

大家可以看到返回值類型是命令對象類型,而且通過@ModelAttribute(“user2”)註解,此時會暴露返回值到模型數據(名字爲user2)中供視圖展示使用。
 
可能有同學會注意到,此時@RequestMapping註解方法的入參user暴露到模型數據中的名字也是user2,那麼到底user2代表哪一個呢?
 
規則是:@ModelAttribute註解的返回值會覆蓋@RequestMapping註解方法中的@ModelAttribute註解的同名命令對象
 
四、匿名綁定命令參數
例子1:
    public String test4(@ModelAttribute UserModel user, Model model)
    或   public String test5(UserModel user, Model model)
說明: 
    此時我們沒有爲命令對象提供暴露到模型數據中的名字,此時的名字是什麼呢?Spring Web MVC自動將簡單類名(首字母小寫)作爲名字暴露,如“cn.javass.springmvc.model.UserModel”暴露的名字爲“userModel”。
例子2:
    public @ModelAttribute List<String> test6()
    或   public @ModelAttribute List<UserModel> test7()
說明: 
    對於集合類型(Collection接口的實現者們,包括數組),生成的模型對象屬性名爲“簡單類名(首字母小寫)”+“List”,如List<String>生成的模型對象屬性名爲“stringList”,List<UserModel>生成的模型對象屬性名爲“userModelList”。
其他情況一律都是使用簡單類名(首字母小寫)作爲模型對象屬性名,如Map<String, UserModel>類型的模型對象屬性名爲“map”。
 
@SessionAttributes :綁定命令對象到session
//1、在控制器類頭上添加@SessionAttributes註解
@SessionAttributes(value = {"user"})    //①
public class SessionAttributeController
 
//2、@ModelAttribute註解的方法進行表單引用對象的創建
@ModelAttribute("user")    //②
public UserModel initUser()
 
//3、@RequestMapping註解方法的@ModelAttribute註解的參數進行命令對象的綁定
@RequestMapping("/session1")   //③
public String session1(@ModelAttribute("user") UserModel user)
 
//4、通過SessionStatus的setComplete()方法清除@SessionAttributes指定的會話數據
@RequestMapping("/session2")   //③
public String session(@ModelAttribute("user") UserModel user, SessionStatus status) {
    if(true) { //④
        status.setComplete();   }
    return "success";
}   
 
 
n@SessionAttributes(value = {“user”})含義:
@SessionAttributes(value = {“user”}) 標識將模型數據中的名字爲“user” 的對象存儲到會話中(默認HttpSession),此處value指定將模型數據中的哪些數據(名字進行匹配)存儲到會話中,此外還有一個types屬性表示模型數據中的哪些類型的對象存儲到會話範圍內,如果同時指定value和types屬性則那些名字和類型都匹配的對象才能存儲到會話範圍內。
n包含@SessionAttributes的執行流程如下所示:
① 首先根據類上的@SessionAttributes註解信息,查找會話內的對象放入到模型數據中;
② 執行@ModelAttribute註解的方法:如果模型數據中包含同名的數據,則不執行@ModelAttribute註解方法進行準備表單引用數據,而是使用①步驟中的會話數據;如果模型數據中不包含同名的數據,執行@ModelAttribute註解的方法並將返回值添加到模型數據中;
 
③ 執行@RequestMapping方法,綁定@ModelAttribute註解的參數:查找模型數據中是否有@ModelAttribute註解的同名對象,如果有直接使用,否則通過反射創建一個;並將請求參數綁定到該命令對象;
此處需要注意:如果使用@SessionAttributes註解控制器類之後,③步驟一定是從模型對象中取得同名的命令對象,如果模型數據中不存在將拋出HttpSessionRequiredException Expected session attribute ‘user’(Spring3.1)
或HttpSessionRequiredException Session attribute ‘user’ required - not found in session(Spring3.0)異常。
 
④ 如果會話可以銷燬了,如多步驟提交表單的最後一步,此時可以調用SessionStatus對象的setComplete()標識當前會話的@SessionAttributes指定的數據可以清理了,此時當@RequestMapping功能處理方法執行完畢會進行清理會話數據。
 
@Value
功能:用於將一個SpEL表達式結果映射到到功能處理方法的參數上
例子:
public String test(@Value("#{systemProperties['java.vm.version']}") String jvmVersion)
 
@MatrixVariable
功能:用於接收URL的path中的矩陣參數
語法格式:XXX/XXX/path;name=value;name=value
開啓功能:
(1)如果是xml配置的RequestMappingHandlerMapping,那麼需要設置removeSemicolonContent屬性爲false
(2)如果是註解的方式,直接設置<mvc:annotation-driven>的enableMatrixVariables=“true“ 就可以了
 
例子代碼見下頁:
 
  測試的URL爲
 
 
@RequestMapping(value = "/users/{userId}/others/{otherUserId}",method = RequestMethod.GET)
public void hello(
//如果只有一個地方有q,也可以這麼取,但如果有多個q,這樣就錯了,必須像第二個那樣去指定取誰的q值
// @MatrixVariable int q,
@MatrixVariable(value="q", pathVar="userId") int q1,
@MatrixVariable(value="q", pathVar="otherUserId") int q2,
@MatrixVariable Map<String, String> matrixVars,
@MatrixVariable(pathVar="userId") Map<String, String> userIdMatrixVars
) {
// System.out.println("q=="+q);
System.out.println("q1="+q1+",q2="+q2+",matrixVars="+matrixVars+",userIdMatrixVars="+userIdMatrixVars);
}
運行結果爲:
q1=11,q2=22,matrixVars={q=[11, 22], r=[12], s=[23]},userIdMatrixVars={q=[11], r=[12]}
 
nSpring3引入一個mvc XML的命名空間用於支持mvc配置,包括如:
<mvc:annotation-driven>:
1:自動註冊基於註解風格的處理器需要的DefaultAnnotationHandlerMapping、AnnotationMethodHandlerAdapter
2:支持Spring3的ConversionService自動註冊
3:支持JSR-303驗證框架的自動探測並註冊(只需把JSR-303實現放置到classpath)
4:自動註冊相應的HttpMessageConverter(用於支持@RequestBody  和 @ResponseBody)(如XML輸入輸出轉換器(只需將JAXP實現放置到classpath)、JSON輸入輸出轉換器(只需將Jackson實現放置到classpath))等。
<mvc:interceptors>:註冊自定義的處理器攔截器;
<mvc:view-controller>:收到相應請求後直接選擇相應的視圖;
<mvc:resources>:邏輯靜態資源路徑到物理靜態資源路徑的支持;
<mvc:default-servlet-handler>:當在web.xml 中DispatcherServlet使用<url-pattern>/</url-pattern> 映射時,能映射靜態資源(當Spring Web MVC框架沒有處理請求對應的控制器時(如一些靜態資源),轉交給默認的Servlet來響應靜態文件,否則報404找不到資源錯誤,)。
 
n參考示例
<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"
    xmlns:mvc= "http://www.springframework.org/schema/mvc"
    xsi:schemaLocation= "
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd"
    default-lazy-init= "true">
<!-- 開啓controller註解支持 -->
    <context:component-scan base-package= "cn.javass" use-default-filters= "false">
        <context:include-filter type= "annotation"expression= "org.springframework.stereotype.Controller"/>
    </context:component-scan>
 
 
<!-- 會自動註冊了validator  ConversionService  -->
    <mvc:annotation-driven
      validator= "validator"
      conversion-service= "conversionService"
      >
    </mvc:annotation-driven>
<!-- ViewResolver -->
<bean name= "defaultViewResolver"class= "org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name= "viewClass"value= "org.springframework.web.servlet.view.JstlView"/>
    <property name= "prefix" value= "/WEB-INF/jsp/"/>
    <property name= "suffix" value= ".jsp"/>
</bean>
<!-- 註冊ConversionService -->
<bean id= "conversionService"class= "org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name= "converters">
       <list>
        <bean class= "cn.javass.springmvc.convert.StringToPhoneNumberConverter"/>
        </list>
    </property>
</bean>
 
 
<!-- 註冊validator -->
<bean id= "validator"
class= "org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
        <property name= "providerClass"  value= "org.hibernate.validator.HibernateValidator"/>
        <!-- 如果不加默認到 使用classpath下的 ValidationMessages.properties -->
        <property name= "validationMessageSource"ref= "messageSource"/>
</bean>
<!-- 註冊消息文件 -->
<bean id= "messageSource"
class= "org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name= "basename" value= "classpath:messages"/>
        <property name= "fileEncodings" value= "utf-8"/>
        <property name= "cacheSeconds" value= "120"/>
</bean>
 

不同的Model有相同的屬性的處理

n不同的Model有相同的屬性
指的是在方法的入參上,可能需要有不同的Model,但是他們又可能有相同的屬性,目前Spring Web MVC默認的方式是,只要同名的屬性,值就是一樣的。
public ModelAndView handleRequest(UserModel um,DepModel dm)
如果:um和dm都有一個uuid的屬性,那麼映射後他們的值會是一樣的
這顯然不合理,在struts2中是通過使用前綴來表示這個uuid的值到底是給誰的,比如:um.uuid,肯定值就給um,而不會給dm。在Spring3 Web Mvc中到底如何實現類似的功能呢?
n使用@InitBinder,在每次進行數據綁定的時候,都會回調這個註解的方法
@InitBinder( "um")
public void initBinderUser(WebDataBinder binder) { 
binder.setFieldDefaultPrefix("um.");   }
@InitBinder( "dm")
public void initBinderDep(WebDataBinder binder) {
binder.setFieldDefaultPrefix("dm.");    }
@RequestMapping(value = "/hello")
public ModelAndView handleRequest(@ModelAttribute( "um") UserModel um,@ModelAttribute( "dm")DepModel dm)
注意:這個方式對URI上的參數無效,僅對parameter區的數據有效
 

靜態資源的處理

 
第一步:使用<mvc:default-servlet-handler/>
獲取系統缺省的Servlet處理器
例子:
<!-- 當在web.xml 中   DispatcherServlet使用 <url-pattern>/</url-pattern> 映射時,能映射靜態資源 -->
<mvc:default-servlet-handler/>
第二步:使用<mvc:resources>
用來指定邏輯靜態資源路徑到物理靜態資源路徑的映射
例子:
<!-- 靜態資源映射 -->
    <mvc:resources mapping= "/static/**" location= "/WEB-INF/static/"/>
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章