1. 程式人生 > >SpringMVC Controller 介紹

SpringMVC Controller 介紹

一、簡介

         在SpringMVC 中,控制器Controller 負責處理由DispatcherServlet 分發的請求,它把使用者請求的資料經過業務處理層處理之後封裝成一個Model ,然後再把該Model 返回給對應的View 進行展示。在SpringMVC 中提供了一個非常簡便的定義Controller 的方法,你無需繼承特定的類或實現特定的介面,只需使用@Controller 標記一個類是Controller ,然後使用@RequestMapping 和@RequestParam 等一些註解用以定義URL 請求和Controller 方法之間的對映,這樣的Controller 就能被外界訪問到。此外Controller 不會直接依賴於HttpServletRequest 和HttpServletResponse 等HttpServlet 物件,它們可以通過Controller 的方法引數靈活的獲取到。為了先對Controller 有一個初步的印象,以下先定義一個簡單的Controller :

Java程式碼  收藏程式碼
  1. @Controller  
  2. public class MyController {  
  3.     @RequestMapping ( "/showView" )  
  4.     public ModelAndView showView() {  
  5.        ModelAndView modelAndView = new ModelAndView();  
  6.        modelAndView.setViewName( "viewName" );  
  7.        modelAndView.addObject( " 需要放到 model 中的屬性名稱 " , " 對應的屬性值,它是一個物件 "
     );  
  8.        return modelAndView;  
  9.     }  
  10. }   
 

在上面的示例中,@Controller 是標記在類MyController 上面的,所以類MyController 就是一個SpringMVC Controller 物件了,然後使用@RequestMapping(“/showView”) 標記在Controller 方法上,表示當請求/showView.do 的時候訪問的是MyController 的showView 方法,該方法返回了一個包括Model 和View 的ModelAndView 物件。這些在後續都將會詳細介紹。

二、使用 @Controller 定義一個 Controller 控制器

         @Controller 用於標記在一個類上,使用它標記的類就是一個SpringMVC Controller 物件。分發處理器將會掃描使用了該註解的類的方法,並檢測該方法是否使用了@RequestMapping 註解。@Controller 只是定義了一個控制器類,而使用@RequestMapping 註解的方法才是真正處理請求的處理器,這個接下來就會講到。

   單單使用@Controller 標記在一個類上還不能真正意義上的說它就是SpringMVC 的一個控制器類,因為這個時候Spring 還不認識它。那麼要如何做Spring 才能認識它呢?這個時候就需要我們把這個控制器類交給Spring 來管理。拿MyController 來舉一個例子

Java程式碼  收藏程式碼
  1. @Controller  
  2. public class MyController {  
  3.     @RequestMapping ( "/showView" )  
  4.     public ModelAndView showView() {  
  5.        ModelAndView modelAndView = new ModelAndView();  
  6.        modelAndView.setViewName( "viewName" );  
  7.        modelAndView.addObject( " 需要放到 model 中的屬性名稱 " , " 對應的屬性值,它是一個物件 " );  
  8.        return modelAndView;  
  9.     }  
  10. }   
 

這個時候有兩種方式可以把MyController 交給Spring 管理,好讓它能夠識別我們標記的@Controller 。

   第一種方式是在SpringMVC 的配置檔案中定義MyController 的bean 物件。

<bean class="com.host.app.web.controller.MyController"/>

   第二種方式是在SpringMVC 的配置檔案中告訴Spring 該到哪裡去找標記為@Controller 的Controller 控制器。

Xml程式碼  收藏程式碼
  1. < context:component-scan base-package = "com.host.app.web.controller" >  
  2.    < context:exclude-filter type = "annotation"  
  3.        expression = "org.springframework.stereotype.Service" />  
  4. </ context:component-scan >   

    注:

       上面 context:exclude-filter 標註的是不掃描 @Service 標註的類

三、使用 @RequestMapping 來對映 Request 請求與處理器

         可以使用@RequestMapping 來對映URL 到控制器類,或者是到Controller 控制器的處理方法上。當@RequestMapping 標記在Controller 類上的時候,裡面使用@RequestMapping 標記的方法的請求地址都是相對於類上的@RequestMapping 而言的;當Controller 類上沒有標記@RequestMapping 註解時,方法上的@RequestMapping 都是絕對路徑。這種絕對路徑和相對路徑所組合成的最終路徑都是相對於根路徑“/ ”而言的。

Java程式碼  收藏程式碼
  1. @Controller  
  2. public class MyController {  
  3.     @RequestMapping ( "/showView" )  
  4.     public ModelAndView showView() {  
  5.        ModelAndView modelAndView = new ModelAndView();  
  6.        modelAndView.setViewName( "viewName" );  
  7.        modelAndView.addObject( " 需要放到 model 中的屬性名稱 " , " 對應的屬性值,它是一個物件 " );  
  8.        return modelAndView;  
  9.     }  
  10. }   
 

在這個控制器中,因為MyController 沒有被@RequestMapping 標記,所以當需要訪問到裡面使用了@RequestMapping 標記的showView 方法時,就是使用的絕對路徑/showView.do 請求就可以了。

Java程式碼  收藏程式碼
  1. @Controller  
  2. @RequestMapping ( "/test" )  
  3. public class MyController {  
  4.     @RequestMapping ( "/showView" )  
  5.     public ModelAndView showView() {  
  6.        ModelAndView modelAndView = new ModelAndView();  
  7.        modelAndView.setViewName( "viewName" );  
  8.        modelAndView.addObject( " 需要放到 model 中的屬性名稱 " , " 對應的屬性值,它是一個物件 " );  
  9.        return modelAndView;  
  10.     }  
  11. }   
 

   這種情況是在控制器上加了@RequestMapping 註解,所以當需要訪問到裡面使用了@RequestMapping 標記的方法showView() 的時候就需要使用showView 方法上@RequestMapping 相對於控制器MyController上@RequestMapping 的地址,即/test/showView.do 。

(一)使用 URI 模板

   URI 模板就是在URI 中給定一個變數,然後在對映的時候動態的給該變數賦值。如URI 模板http://localhost/app/{variable1}/index.html ,這個模板裡面包含一個變數variable1 ,那麼當我們請求http://localhost/app/hello/index.html 的時候,該URL 就跟模板相匹配,只是把模板中的variable1 用hello 來取代。在SpringMVC 中,這種取代模板中定義的變數的值也可以給處理器方法使用,這樣我們就可以非常方便的實現URL 的RestFul 風格。這個變數在SpringMVC 中是使用@PathVariable 來標記的。

   在SpringMVC 中,我們可以使用@PathVariable 來標記一個Controller 的處理方法引數,表示該引數的值將使用URI 模板中對應的變數的值來賦值。

Java程式碼  收藏程式碼
  1. @Controller  
  2. @RequestMapping ( "/test/{variable1}" )  
  3. public class MyController {  
  4.     @RequestMapping ( "/showView/{variable2}" )  
  5.     public ModelAndView showView( @PathVariable String variable1, @PathVariable ( "variable2" ) int variable2) {  
  6.        ModelAndView modelAndView = new ModelAndView();  
  7.        modelAndView.setViewName( "viewName" );  
  8.        modelAndView.addObject( " 需要放到 model 中的屬性名稱 " , " 對應的屬性值,它是一個物件 " );  
  9.        return modelAndView;  
  10.     }  
  11. }   
 

   在上面的程式碼中我們定義了兩個URI 變數,一個是控制器類上的variable1 ,一個是showView 方法上的variable2 ,然後在showView 方法的引數裡面使用@PathVariable 標記使用了這兩個變數。所以當我們使用/test/hello/showView/2.do 來請求的時候就可以訪問到MyController 的showView 方法,這個時候variable1 就被賦予值hello ,variable2 就被賦予值2 ,然後我們在showView 方法引數裡面標註了引數variable1 和variable2 是來自訪問路徑的path 變數,這樣方法引數variable1 和variable2 就被分別賦予hello 和2 。方法引數variable1 是定義為String 型別,variable2 是定義為int 型別,像這種簡單型別在進行賦值的時候Spring 是會幫我們自動轉換的,關於複雜型別該如何來轉換在後續內容中將會講到。

   在上面的程式碼中我們可以看到在標記variable1 為path 變數的時候我們使用的是@PathVariable ,而在標記variable2 的時候使用的是@PathVariable(“variable2”) 。這兩者有什麼區別呢?第一種情況就預設去URI 模板中找跟引數名相同的變數,但是這種情況只有在使用debug 模式進行編譯的時候才可以,而第二種情況是明確規定使用的就是URI 模板中的variable2 變數。當不是使用debug 模式進行編譯,或者是所需要使用的變數名跟引數名不相同的時候,就要使用第二種方式明確指出使用的是URI 模板中的哪個變數。

   除了在請求路徑中使用URI 模板,定義變數之外,@RequestMapping 中還支援萬用字元“* ”。如下面的程式碼我就可以使用/myTest/whatever/wildcard.do 訪問到Controller 的testWildcard 方法。

Java程式碼  收藏程式碼
  1. @Controller  
  2. @RequestMapping ( "/myTest" )  
  3. public class MyController {  
  4.     @RequestMapping ( "*/wildcard" )  
  5.     public String testWildcard() {  
  6.        System. out .println( "wildcard------------" );  
  7.        return "wildcard" ;  
  8.     }    
  9. }   
 

(二)使用 @RequestParam 繫結 HttpServletRequest 請求引數到控制器方法引數

Java程式碼  收藏程式碼
  1. @RequestMapping ( "requestParam" )  
  2. ublic String testRequestParam( @RequestParam(required=false) String name, @RequestParam ( "age" ) int age) {  
  3.    return "requestParam" ;  
  4. }   
 

在上面程式碼中利用@RequestParam 從HttpServletRequest 中綁定了引數name 到控制器方法引數name ,綁定了引數age 到控制器方法引數age 。值得注意的是和@PathVariable 一樣,當你沒有明確指定從request 中取哪個引數時,Spring 在程式碼是debug 編譯的情況下會預設取更方法引數同名的引數,如果不是debug 編譯的就會報錯。此外,當需要從request 中繫結的引數和方法的引數名不相同的時候,也需要在@RequestParam 中明確指出是要繫結哪個引數。在上面的程式碼中如果我訪問/requestParam.do?name=hello&age=1 則Spring 將會把request請求引數name 的值hello 賦給對應的處理方法引數name ,把引數age 的值1 賦給對應的處理方法引數age 。

在@RequestParam 中除了指定繫結哪個引數的屬性value 之外,還有一個屬性required ,它表示所指定的引數是否必須在request 屬性中存在,預設是true ,表示必須存在,當不存在時就會報錯。在上面程式碼中我們指定了引數name 的required 的屬性為false ,而沒有指定age 的required 屬性,這時候如果我們訪問/requestParam.do而沒有傳遞引數的時候,系統就會丟擲異常,因為age 引數是必須存在的,而我們沒有指定。而如果我們訪問/requestParam.do?age=1 的時候就可以正常訪問,因為我們傳遞了必須的引數age ,而引數name 是非必須的,不傳遞也可以。

(三)使用 @CookieValue 繫結 cookie 的值到 Controller 方法引數

Java程式碼  收藏程式碼
  1. @RequestMapping ( "cookieValue" )  
  2. public String testCookieValue( @CookieValue ( "hello" ) String cookieValue, @CookieValue String hello) {  
  3.    System. out .println(cookieValue + "-----------" + hello);  
  4.    return "cookieValue" ;  
  5. }   
 

    在上面的程式碼中我們使用@CookieValue 綁定了cookie 的值到方法引數上。上面一共綁定了兩個引數,一個是明確指定要繫結的是名稱為hello 的cookie 的值,一個是沒有指定。使用沒有指定的形式的規則和@PathVariable、@RequestParam 的規則是一樣的,即在debug 編譯模式下將自動獲取跟方法引數名同名的cookie 值。

(四)使用 @RequestHeader 註解繫結 HttpServletRequest 頭資訊到Controller 方法引數

Java程式碼  收藏程式碼
  1. @RequestMapping ( "testRequestHeader" )  
  2. public String testRequestHeader( @RequestHeader ( "Host" ) String hostAddr, @RequestHeader String Host, @RequestHeader String host ) {  
  3.     System. out .println(hostAddr + "-----" + Host + "-----" + host );  
  4.     return "requestHeader" ;  
  5. }   
 

         在上面的程式碼中我們使用了 @RequestHeader 綁定了 HttpServletRequest 請求頭 host 到Controller 的方法引數。上面方法的三個引數都將會賦予同一個值,由此我們可以知道在繫結請求頭引數到方法引數的時候規則和 @PathVariable 、 @RequestParam 以及 @CookieValue 是一樣的,即沒有指定繫結哪個引數到方法引數的時候,在 debug 編譯模式下將使用方法引數名作為需要繫結的引數。但是有一點 @RequestHeader 跟另外三種繫結方式是不一樣的,那就是在使用 @RequestHeader 的時候是大小寫不敏感的,即 @RequestHeader(“Host”) 和 @RequestHeader(“host”) 繫結的都是 Host 頭資訊。記住在 @PathVariable 、 @RequestParam 和 @CookieValue 中都是大小寫敏感的。

(五) @RequestMapping 的一些高階應用

         在RequestMapping 中除了指定請求路徑value 屬性外,還有其他的屬性可以指定,如params 、method 和headers 。這樣屬性都可以用於縮小請求的對映範圍。

1.params屬性

   params 屬性用於指定請求引數的,先看以下程式碼。

Java程式碼  收藏程式碼
  1. @RequestMapping (value= "testParams" , params={ "param1=value1" , "param2" , "!param3" })  
  2. public String testParams() {  
  3.    System. out .println( "test Params..........." );  
  4.    return "testParams" ;  
  5. }   
 

   在上面的程式碼中我們用@RequestMapping 的params 屬性指定了三個引數,這些引數都是針對請求引數而言的,它們分別表示引數param1 的值必須等於value1 ,引數param2 必須存在,值無所謂,引數param3 必須不存在,只有當請求/testParams.do 並且滿足指定的三個引數條件的時候才能訪問到該方法。所以當請求/testParams.do?param1=value1&param2=value2 的時候能夠正確訪問到該testParams 方法,當請求/testParams.do?param1=value1&param2=value2&param3=value3 的時候就不能夠正常的訪問到該方法,因為在@RequestMapping 的params 引數裡面指定了引數param3 是不能存在的。

2.method屬性

   method 屬性主要是用於限制能夠訪問的方法型別的。

Java程式碼  收藏程式碼
  1. @RequestMapping (value= "testMethod" , method={RequestMethod. GET , RequestMethod. DELETE })  
  2. public String testMethod() {  
  3.    return "method" ;  
  4. }   
 

在上面的程式碼中就使用method 引數限制了以GET 或DELETE 方法請求/testMethod.do 的時候才能訪問到該Controller 的testMethod 方法。

3.headers屬性

         使用headers 屬性可以通過請求頭資訊來縮小@RequestMapping 的對映範圍。

Java程式碼  收藏程式碼
  1. @RequestMapping (value= "testHeaders" , headers={ "host=localhost" , "Accept" })  
  2. public String testHeaders() {  
  3.    return "headers" ;  
  4. }   
 

   headers 屬性的用法和功能與params 屬性相似。在上面的程式碼中當請求/testHeaders.do 的時候只有當請求頭包含Accept 資訊,且請求的host 為localhost 的時候才能正確的訪問到testHeaders 方法。

(六)以 @RequestMapping 標記的處理器方法支援的方法引數和返回型別

1. 支援的方法引數型別

         (1 )HttpServlet 物件,主要包括HttpServletRequest 、HttpServletResponse 和HttpSession 物件。 這些引數Spring 在呼叫處理器方法的時候會自動給它們賦值,所以當在處理器方法中需要使用到這些物件的時候,可以直接在方法上給定一個方法引數的申明,然後在方法體裡面直接用就可以了。但是有一點需要注意的是在使用HttpSession 物件的時候,如果此時HttpSession 物件還沒有建立起來的話就會有問題。

   (2 )Spring 自己的WebRequest 物件。 使用該物件可以訪問到存放在HttpServletRequest 和HttpSession 中的屬性值。

   (3 )InputStream 、OutputStream 、Reader 和Writer 。 InputStream 和Reader 是針對HttpServletRequest 而言的,可以從裡面取資料;OutputStream 和Writer 是針對HttpServletResponse 而言的,可以往裡面寫資料。

   (4 )使用@PathVariable 、@RequestParam 、@CookieValue 和@RequestHeader 標記的引數。

   (5 )使用@ModelAttribute 標記的引數。

   (6 )java.util.Map 、Spring 封裝的Model 和ModelMap 。 這些都可以用來封裝模型資料,用來給檢視做展示。

   (7 )實體類。 可以用來接收上傳的引數。

   (8 )Spring 封裝的MultipartFile 。 用來接收上傳檔案的。

   (9 )Spring 封裝的Errors 和BindingResult 物件。 這兩個物件引數必須緊接在需要驗證的實體物件引數之後,它裡面包含了實體物件的驗證結果。

2. 支援的返回型別

         (1 )一個包含模型和檢視的ModelAndView 物件。

   (2 )一個模型物件,這主要包括Spring 封裝好的Model 和ModelMap ,以及java.util.Map ,當沒有檢視返回的時候檢視名稱將由RequestToViewNameTranslator 來決定。

   (3 )一個View 物件。這個時候如果在渲染檢視的過程中模型的話就可以給處理器方法定義一個模型引數,然後在方法體裡面往模型中新增值。

   (4 )一個String 字串。這往往代表的是一個檢視名稱。這個時候如果需要在渲染檢視的過程中需要模型的話就可以給處理器方法一個模型引數,然後在方法體裡面往模型中新增值就可以了。

   (5 )返回值是void 。這種情況一般是我們直接把返回結果寫到HttpServletResponse 中了,如果沒有寫的話,那麼Spring 將會利用RequestToViewNameTranslator 來返回一個對應的檢視名稱。如果檢視中需要模型的話,處理方法與返回字串的情況相同。

   (6 )如果處理器方法被註解@ResponseBody 標記的話,那麼處理器方法的任何返回型別都會通過HttpMessageConverters 轉換之後寫到HttpServletResponse 中,而不會像上面的那些情況一樣當做檢視或者模型來處理。

   (7 )除以上幾種情況之外的其他任何返回型別都會被當做模型中的一個屬性來處理,而返回的檢視還是由RequestToViewNameTranslator 來決定,新增到模型中的屬性名稱可以在該方法上用@ModelAttribute(“attributeName”) 來定義,否則將使用返回型別的類名稱的首字母小寫形式來表示。使用@ModelAttribute 標記的方法會在@RequestMapping 標記的方法執行之前執行。

(七)使用 @ModelAttribute 和 @SessionAttributes 傳遞和儲存資料

       SpringMVC 支援使用 @ModelAttribute 和 @SessionAttributes 在不同的模型和控制器之間共享資料。 @ModelAttribute 主要有兩種使用方式,一種是標註在方法上,一種是標註在 Controller 方法引數上。

當 @ModelAttribute 標記在方法上的時候,該方法將在處理器方法執行之前執行,然後把返回的物件存放在 session 或模型屬性中,屬性名稱可以使用 @ModelAttribute(“attributeName”) 在標記方法的時候指定,若未指定,則使用返回型別的類名稱(首字母小寫)作為屬性名稱。關於 @ModelAttribute 標記在方法上時對應的屬性是存放在 session 中還是存放在模型中,我們來做一個實驗,看下面一段程式碼。

Java程式碼  收藏程式碼
  1. @Controller  
  2. @RequestMapping ( "/myTest" )  
  3. public class MyController {  
  4.     @ModelAttribute ( "hello" )  
  5.     public String getModel() {  
  6.        System. out .println( "-------------Hello---------" );  
  7.        return "world" ;  
  8.     }  
  9.     @ModelAttribute ( "intValue" )  
  10.     public int getInteger() {  
  11.        System. out .println( "-------------intValue---------------" );  
  12.        return 10;  
  13.     }  
  14.     @RequestMapping ( "sayHello" )  
  15.     public void sayHello( @ModelAttribute ( "hello" ) String hello, @ModelAttribute ( "intValue" ) int num, @ModelAttribute ( "user2" ) User user, Writer writer, HttpSession session) throws IOException {  
  16.        writer.write( "Hello " + hello + " , Hello " + user.getUsername() + num);  
  17.        writer.write( "\r" );  
  18.        Enumeration enume = session.getAttributeNames();  
  19.        while (enume.hasMoreElements())  
  20.            writer.write(enume.nextElement() + "\r" );  
  21.     }  
  22.     @ModelAttribute ( "user2" )  
  23.     public User getUser() {  
  24.        System. out .println( "---------getUser-------------" );  
  25.        return new User(3"user2" );  
  26.     }  
  27. }   
 

當我們請求 /myTest/sayHello.do 的時候使用 @ModelAttribute 標記的方法會先執行,然後把它們返回的物件存放到模型中。最終訪問到 sayHello 方法的時候,使用 @ModelAttribute 標記的方法引數都能被正確的注入值。執行結果如下圖所示:


       由執行結果我們可以看出來,此時 session 中沒有包含任何屬性,也就是說上面的那些物件都是存放在模型屬性中,而不是存放在 session 屬性中。那要如何才能存放在 session 屬性中呢?這個時候我們先引入一個新的概念 @SessionAttributes ,它的用法會在講完 @ModelAttribute 之後介紹,這裡我們就先拿來用一下。我們在 MyController 類上加上 @SessionAttributes 屬性標記哪些是需要存放到 session 中的。看下面的程式碼:

Java程式碼  收藏程式碼
  1. @Controller  
  2. @RequestMapping ( "/myTest" )  
  3. @SessionAttributes (value={ "intValue" , "stringValue" }, types={User. class })  
  4. public class MyController {  
  5.     @ModelAttribute ( "hello" )  
  6.     public String getModel() {  
  7.        System. out .println( "-------------Hello---------" );  
  8.        return "world" ;  
  9.     }  
  10.     @ModelAttribute ( "intValue" )  
  11.     public int getInteger() {  
  12.        System. out .println( "-------------intValue---------------" );  
  13.        return 10;  
  14.     }  
  15.     @RequestMapping ( "sayHello" )  
  16.     public void sayHello(Map<String, Object> map, @ModelAttribute ( "hello" ) String hello, @ModelAttribute ( "intValue" ) int num, @ModelAttribute ( "user2" ) User user, Writer writer, HttpServletRequest request) throws IOException {  
  17.        map.put( "stringValue" , "String" );  
  18.        writer.write( "Hello " + hello + " , Hello " + user.getUsername() + num);  
  19.        writer.write( "\r" );  
  20.        HttpSession session = request.getSession();  
  21.        Enumeration enume = session.getAttributeNames();  
  22.        while (enume.hasMoreElements())  
  23.            writer.write(enume.nextElement() + "\r" );  
  24.        System. out .println(session);  
  25.     }  
  26.     @ModelAttribute ( "user2" )  
  27.     public User getUser() {  
  28.        System. out .println( "---------getUser-------------" );  
  29.        return new User(3"user2" );  
  30.     }  
  31. }   
 

       在上面程式碼中我們指定了屬性為 intValue 或 stringValue 或者型別為 User 的都會放到 Session中,利用上面的程式碼當我們訪問 /myTest/sayHello.do 的時候,結果如下:


       仍然沒有打印出任何 session 屬性,這是怎麼回事呢?怎麼定義了把模型中屬性名為 intValue 的物件和型別為 User 的物件存到 session 中,而實際上沒有加進去呢?難道我們錯啦?我們當然沒有錯,只是在第一次訪問 /myTest/sayHello.do 的時候 @SessionAttributes 定義了需要存放到 session 中的屬性,而且這個模型中也有對應的屬性,但是這個時候還沒有加到 session 中,所以 session 中不會有任何屬性,等處理器方法執行完成後 Spring 才會把模型中對應的屬性新增到 session 中。所以當請求第二次的時候就會出現如下結果:


當 @ModelAttribute 標記在處理器方法引數上的時候,表示該引數的值將從模型或者 Session 中取對應名稱的屬性值,該名稱可以通過 @ModelAttribute(“attributeName”) 來指定,若未指定,則使用引數型別的類名稱(首字母小寫)作為屬性名稱。

Java程式碼  收藏程式碼
  1. @Controller  
  2. @RequestMapping ( "/myTest" )  
  3. public class MyController {  
  4.     @ModelAttribute ( "hello" )  
  5.     public String getModel() {  
  6.        return "world" ;  
  7.     }  
  8.     @RequestMapping ( "sayHello" )  
  9.     public void sayHello( @ModelAttribute ( "hello" ) String hello, Writer writer) throws IOException {  
  10.        writer.write( "Hello " + hello);  
  11.     }     
  12. }   
 

在上面程式碼中,當我們請求/myTest/sayHello.do 的時候,由於MyController 中的方法getModel 使用了註解@ModelAttribute 進行標記,所以在執行請求方法sayHello 之前會先執行getModel 方法,這個時候getModel 方法返回一個字串world 並把它以屬性名hello 儲存在模型中,接下來訪問請求方法sayHello 的時候,該方法的hello引數使用@ModelAttribute(“hello”) 進行標記,這意味著將從session 或者模型中取屬性名稱為hello 的屬性值賦給hello 引數,所以這裡hello 引數將被賦予值world ,所以請求完成後將會在頁面上看到Hello world 字串。

@SessionAttributes 用於標記需要在Session 中使用到的資料,包括從Session 中取資料和存資料。@SessionAttributes 一般是標記在Controller 類上的,可以通過名稱、型別或者名稱加型別的形式來指定哪些屬性是需要存放在session 中的。

Java程式碼  收藏程式碼
  1. @Controller  
  2. @RequestMapping ( "/myTest" )  
  3. @SessionAttributes (value={ "user1" , "blog1" }, types={User. class , Blog. class })  
  4. public class MyController {  
  5.     @RequestMapping ( "setSessionAttribute" )  
  6.     public void setSessionAttribute(Map<String, Object> map, Writer writer) throws IOException {  
  7.        User user = new User(1"user" );  
  8.        User user1 = new User(2"user1" );  
  9.        Blog blog = new Blog(1"blog" );  
  10.        Blog blog1 = new Blog(2"blog1" );  
  11.        map.put( "user" , user);  
  12.        map.put( "user1" , user1);  
  13.        map.put( "blog" , blog);  
  14.        map.put( "blog1" , blog1);  
  15.        writer.write( "over." );  
  16.     }  
  17.     @RequestMapping ( "useSessionAttribute" )  
  18.     public void useSessionAttribute(Writer writer, @ModelAttribute ( "user1" ) User user1, @ModelAttribute ( "blog1" ) Blog blog1) throws IOException {  
  19.        writer.write(user1.getId() + "--------" + user1.getUsername());  
  20.        writer.write( "\r" );  
  21.        writer.write(blog1.getId() + "--------" + blog1.getTitle());  
  22.     }  
  23.     @RequestMapping ( "useSessionAttribute2" )  
  24.     public void useSessionAttribute(Writer writer, @ModelAttribute ( "user1" ) User user1, @ModelAttribute ( "blog1" ) Blog blog1, @ModelAttribute User user, HttpSession session) throws IOException {  
  25.        writer.write(user1.getId() + "--------" + user1.getUsername());  
  26.        writer.write( "\r" );  
  27.        writer.write(blog1.getId() + "--------" + blog1.getTitle());  
  28.        writer.write( "\r" );  
  29.        writer.write(user.getId() + "---------" + user.getUsername());  
  30.        writer.write( "\r" );  
  31.        Enumeration enume = session.getAttributeNames();  
  32.        while (enume.hasMoreElements())  
  33.            writer.write(enume.nextElement() + " \r" );  
  34.     }  
  35.     @RequestMapping ( "useSessionAttribute3" )  
  36.     public void useSessionAttribute( @ModelAttribute ( "user2" ) User user) {  
  37.     }  
  38. }   
 

   在上面程式碼中我們可以看到在MyController 上面使用了@SessionAttributes 標記了需要使用到的Session 屬性。可以通過名稱和型別指定需要存放到Session 中的屬性,對應@SessionAttributes 註解的value 和types 屬性。當使用的是types 屬性的時候,那麼使用的Session 屬性名稱將會是對應型別的名稱(首字母小寫)。當value 和types 兩個屬性都使用到了的時候,這時候取的是它們的並集,而不是交集,所以上面程式碼中指定要存放在Session 中的屬性有名稱為user1 或blog1 的物件,或型別為User 或Blog 的物件。在上面程式碼中我們首先訪問/myTest/setSessionAttribute.do ,該請求將會請求到MyController 的setSessionAttribute 方法,在該方法中,我們往模型裡面添加了user 、user1 、blog 和blog1 四個屬性,因為它們或跟類上的@SessionAttributes 定義的需要存到session 中的屬性名稱相同或型別相同,所以在請求完成後這四個屬性都將新增到session 屬性中。接下來訪問/myTest/useSessionAttribute.do ,該請求將會請求MyController 的useSessionAttribute(Writer writer, @ModelAttribute(“user1”) User user1, @ModelAttribute(“blog1”) Blog blog) 方法,該方法引數中用@ModelAttribute 指定了引數user1 和引數blog1 是需要從session 或模型中繫結的,恰好這個時候session 中已經有了這兩個屬性,所以這個時候在方法執行之前會先繫結這兩個引數。執行結果如下圖所示:


   接下來訪問/myTest/useSessionAttribute2.do ,這個時候請求的是上面程式碼中對應的第二個useSessionAttribute方法,方法引數user 、user1 和blog1 用@ModelAttribute 聲明瞭需要session 或模型屬性注入,我們知道在請求/myTest/setSessionAttribute.do 的時候這些屬性都已經新增到了session 中,所以該請求的結果會如下圖所示:


   接下來訪問/myTest/useSessionAttribute3.do ,這個時候請求的是上面程式碼中對應的第三個useSessionAttribute方法,我們可以看到該方法的方法引數user 使用了@ModelAttribute(“user2”) 進行標記,表示user 需要session中的user2 屬性來注入,但是這個時候我們知道session 中是不存在user2 屬性的,所以這個時候就會報錯了。執行結果如圖所示:


(八)定製自己的型別轉換器

         在通過處理器方法引數接收 request 請求引數繫結資料的時候,對於一些簡單的資料型別 Spring 會幫我們自動進行型別轉換,而對於一些複雜的型別由於 Spring 沒法識別,所以也就不能幫助我們進行自動轉換了,這個時候如果我們需要 Spring 來幫我們自動轉換的話就需要我們給 Spring 註冊一個對特定型別的識別轉換器。 Spring 允許我們提供兩種型別的識別轉換器,一種是註冊在 Controller 中的,一種是註冊在SpringMVC 的配置檔案中。聰明的讀者看到這裡應該可以想到它們的區別了,定義在 Controller 中的是區域性的,只在當前 Controller 中有效,而放在 SpringMVC 配置檔案中的是全域性的,所有 Controller 都可以拿來使用。

1. 在 @InitBinder 標記的方法中定義區域性的型別轉換器

         我們可以使用 @InitBinder 註解標註在 Controller 方法上,然後在方法體裡面註冊資料繫結的轉換器,這主要是通過 WebDataBinder 進行的。我們可以給需要註冊資料繫結的轉換器的方法一個WebDataBinder 引數,然後給該方法加上 @InitBinder 註解,這樣當該 Controller 中在處理請求方法時如果發現有不能解析的物件的時候,就會看該類中是否有使用 @InitBinder 標記的方法,如果有就會執行該方法,然後看裡面定義的型別轉換器是否與當前需要的型別匹配。

Java程式碼  收藏程式碼
  1. @Controller  
  2. @RequestMapping ( "/myTest" )  
  3. public class MyController {  
  4.     @InitBinder  
  5.     public void dataBinder(WebDataBinder binder) {  
  6.        DateFormat dateFormat = new SimpleDateFormat( "yyyyMMdd" );  
  7.        PropertyEditor propertyEditor = new CustomDateEditor(dateFormat, true ); // 第二個引數表示是否允許為空  
  8.        binder.registerCustomEditor(Date. class , propertyEditor);  
  9.     }  
  10.     @RequestMapping ( "dataBinder/{date}" )  
  11.     public void testDate( @PathVariable Date date, Writer writer) throws IOException {  
  12.        writer.write(String.valueOf (date.getTime()));  
  13.     }  
  14. }   
 

       在上面的程式碼中當我們請求 /myTest/dataBinder/20121212.do 的時候, Spring 就會利用@InitBinder 標記的方法裡面定義的型別轉換器把字串 20121212 轉換為一個 Date 物件。這樣定義的型別轉換器是區域性的型別轉換器,一旦出了這個 Controller 就不會再起作用。型別轉換器是通過WebDataBinder 物件的 registerCustomEditor 方法來註冊的,要實現自己的型別轉換器就要實現自己的 PropertyEditor 物件。 Spring 已經給我們提供了一些常用的屬性編輯器,如 CustomDateEditor 、CustomBooleanEditor 等。

       PropertyEditor 是一個介面,要實現自己的 PropertyEditor 類我們可以實現這個介面,然後實現裡面的方法。但是 PropertyEditor 裡面定義的方法太多了,這樣做比較麻煩。在 java 中有一個封裝類是實現了 PropertyEditor 介面的,它是 PropertyEditorSupport 類。所以如果需要實現自己的PropertyEditor 的時候只需要繼承 PropertyEditorSupport 類,然後重寫其中的一些方法。一般就是重寫 setAsText 和 getAsText 方法就可以了, setAsText 方法是用