SpringMVC Controller 介紹

如下資源寫的很好,感謝原創做者,原創地址,特收藏!

1、簡介

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

Java代碼  
@Controller
public class MyController {

    @RequestMapping ( "/showView" )
    public ModelAndView showView() {
       ModelAndView modelAndView = new ModelAndView();
       modelAndView.setViewName( "viewName" );
       modelAndView.addObject( " 須要放到 model 中的屬性名稱 " , " 對應的屬性值,它是一個對象 " );
       return modelAndView;
    }

} 

 

 

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

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

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

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

 

Java代碼  
@Controller
public class MyController {
    @RequestMapping ( "/showView" )
    public ModelAndView showView() {
       ModelAndView modelAndView = new ModelAndView();
       modelAndView.setViewName( "viewName" );
       modelAndView.addObject( " 須要放到 model 中的屬性名稱 " , " 對應的屬性值,它是一個對象 " );
       return modelAndView;
    }

} 

 

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

   第一種方式是在SpringMVC 的配置文件中定義MyController 的bean 對象。cookie

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

   第二種方式是在SpringMVC 的配置文件中告訴Spring 該到哪裏去找標記爲@Controller 的Controller 控制器。mvc

Xml代碼  
    < context:component-scan base-package = "com.host.app.web.controller" >
       < context:exclude-filter type = "annotation"
           expression = "org.springframework.stereotype.Service" />
    </ context:component-scan > 

 

    注:app

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

3、使用 @RequestMapping 來映射 Request 請求與處理器

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

 

Java代碼  
@Controller
public class MyController {
    @RequestMapping ( "/showView" )
    public ModelAndView showView() {
       ModelAndView modelAndView = new ModelAndView();
       modelAndView.setViewName( "viewName" );
       modelAndView.addObject( " 須要放到 model 中的屬性名稱 " , " 對應的屬性值,它是一個對象 " );
       return modelAndView;
    }

} 

 

 

在這個控制器中,由於MyController 沒有被@RequestMapping 標記,因此當須要訪問到裏面使用了@RequestMapping 標記的showView 方法時,就是使用的絕對路徑/showView.do 請求就能夠了。

 

Java代碼  
@Controller
@RequestMapping ( "/test" )
public class MyController {
    @RequestMapping ( "/showView" )
    public ModelAndView showView() {
       ModelAndView modelAndView = new ModelAndView();
       modelAndView.setViewName( "viewName" );
       modelAndView.addObject( " 須要放到 model 中的屬性名稱 " , " 對應的屬性值,它是一個對象 " );
       return modelAndView;
    }

} 

 

 

   這種狀況是在控制器上加了@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代碼  

 

@Controller
@RequestMapping ( "/test/{variable1}" )
public class MyController {

    @RequestMapping ( "/showView/{variable2}" )
    public ModelAndView showView( @PathVariable String variable1, @PathVariable ( "variable2" ) int variable2) {
       ModelAndView modelAndView = new ModelAndView();
       modelAndView.setViewName( "viewName" );
       modelAndView.addObject( " 須要放到 model 中的屬性名稱 " , " 對應的屬性值,它是一個對象 " );
       return modelAndView;
    }
} 

 

   在上面的代碼中咱們定義了兩個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代碼  
@Controller
@RequestMapping ( "/myTest" )
public class MyController {
    @RequestMapping ( "*/wildcard" )
    public String testWildcard() {
       System. out .println( "wildcard------------" );
       return "wildcard" ;
    }  
} 

 

 

(二)使用 @RequestParam 綁定 HttpServletRequest 請求參數到控制器方法參數

 

Java代碼  
    @RequestMapping ( "requestParam" )
   public String testRequestParam( @RequestParam(required=false) String name, @RequestParam ( "age" ) int age) {
       return "requestParam" ;
    } 

 

 

在上面代碼中利用@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代碼  
@RequestMapping ( "testRequestHeader" )
public String testRequestHeader( @RequestHeader ( "Host" ) String hostAddr, @RequestHeader String Host, @RequestHeader String host ) {
    System. out .println(hostAddr + "-----" + Host + "-----" + host );
    return "requestHeader" ;
} 

 

 

         在上面的代碼中咱們使用了 @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代碼  
    @RequestMapping (value= "testParams" , params={ "param1=value1" , "param2" , "!param3" })
    public String testParams() {
       System. out .println( "test Params..........." );
       return "testParams" ;
    } 

 

   在上面的代碼中咱們用@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代碼  
    @RequestMapping (value= "testMethod" , method={RequestMethod. GET , RequestMethod. DELETE })
    public String testMethod() {
       return "method" ;
    } 
 

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

3.headers屬性

         使用headers 屬性能夠經過請求頭信息來縮小@RequestMapping 的映射範圍。

Java代碼  
    @RequestMapping (value= "testHeaders" , headers={ "host=localhost" , "Accept" })
    public String testHeaders() {
       return "headers" ;
    } 
 

   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代碼  

 

@Controller
@RequestMapping ( "/myTest" )
public class MyController {

    @ModelAttribute ( "hello" )
    public String getModel() {
       System. out .println( "-------------Hello---------" );
       return "world" ;
    }

    @ModelAttribute ( "intValue" )
    public int getInteger() {
       System. out .println( "-------------intValue---------------" );
       return 10;
    }

    @RequestMapping ( "sayHello" )
    public void sayHello( @ModelAttribute ( "hello" ) String hello, @ModelAttribute ( "intValue" ) int num, @ModelAttribute ( "user2" ) User user, Writer writer, HttpSession session) throws IOException {
       writer.write( "Hello " + hello + " , Hello " + user.getUsername() + num);
       writer.write( "\r" );
       Enumeration enume = session.getAttributeNames();
       while (enume.hasMoreElements())
           writer.write(enume.nextElement() + "\r" );
    }

    @ModelAttribute ( "user2" )
    public User getUser() {
       System. out .println( "---------getUser-------------" );
       return new User(3, "user2" );
    }
} 

 

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

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

Java代碼  
@Controller
@RequestMapping ( "/myTest" )
@SessionAttributes (value={ "intValue" , "stringValue" }, types={User. class })
public class MyController {

    @ModelAttribute ( "hello" )
    public String getModel() {
       System. out .println( "-------------Hello---------" );
       return "world" ;
    }

    @ModelAttribute ( "intValue" )
    public int getInteger() {
       System. out .println( "-------------intValue---------------" );
       return 10;
    }
   
    @RequestMapping ( "sayHello" )
    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 {
       map.put( "stringValue" , "String" );
       writer.write( "Hello " + hello + " , Hello " + user.getUsername() + num);
       writer.write( "\r" );
       HttpSession session = request.getSession();
       Enumeration enume = session.getAttributeNames();
       while (enume.hasMoreElements())
           writer.write(enume.nextElement() + "\r" );
       System. out .println(session);
    }

    @ModelAttribute ( "user2" )
    public User getUser() {
       System. out .println( "---------getUser-------------" );
       return new User(3, "user2" );
    }
} 

 

       在上面代碼中咱們指定了屬性爲 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代碼  
@Controller
@RequestMapping ( "/myTest" )
public class MyController {

    @ModelAttribute ( "hello" )
    public String getModel() {
       return "world" ;
    }

    @RequestMapping ( "sayHello" )
    public void sayHello( @ModelAttribute ( "hello" ) String hello, Writer writer) throws IOException {
       writer.write( "Hello " + hello);
    }   
} 

 

在上面代碼中,當咱們請求/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代碼  
@Controller
@RequestMapping ( "/myTest" )
@SessionAttributes (value={ "user1" , "blog1" }, types={User. class , Blog. class })
public class MyController {

    @RequestMapping ( "setSessionAttribute" )
    public void setSessionAttribute(Map<String, Object> map, Writer writer) throws IOException {
       User user = new User(1, "user" );
       User user1 = new User(2, "user1" );
       Blog blog = new Blog(1, "blog" );
       Blog blog1 = new Blog(2, "blog1" );
       map.put( "user" , user);
       map.put( "user1" , user1);
       map.put( "blog" , blog);
       map.put( "blog1" , blog1);
       writer.write( "over." );
    }

 

    @RequestMapping ( "useSessionAttribute" )
    public void useSessionAttribute(Writer writer, @ModelAttribute ( "user1" ) User user1, @ModelAttribute ( "blog1" ) Blog blog1) throws IOException {
       writer.write(user1.getId() + "--------" + user1.getUsername());
       writer.write( "\r" );
       writer.write(blog1.getId() + "--------" + blog1.getTitle());
    }

    @RequestMapping ( "useSessionAttribute2" )
    public void useSessionAttribute(Writer writer, @ModelAttribute ( "user1" ) User user1, @ModelAttribute ( "blog1" ) Blog blog1, @ModelAttribute User user, HttpSession session) throws IOException {
       writer.write(user1.getId() + "--------" + user1.getUsername());
       writer.write( "\r" );
       writer.write(blog1.getId() + "--------" + blog1.getTitle());
       writer.write( "\r" );
       writer.write(user.getId() + "---------" + user.getUsername());
       writer.write( "\r" );
       Enumeration enume = session.getAttributeNames();
       while (enume.hasMoreElements())
           writer.write(enume.nextElement() + " \r" );
    }

    @RequestMapping ( "useSessionAttribute3" )
    public void useSessionAttribute( @ModelAttribute ( "user2" ) User user) {

    }
} 

 

   在上面代碼中咱們能夠看到在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代碼  
@Controller
@RequestMapping ( "/myTest" )
public class MyController {

    @InitBinder
    public void dataBinder(WebDataBinder binder) {
       DateFormat dateFormat = new SimpleDateFormat( "yyyyMMdd" );
       PropertyEditor propertyEditor = new CustomDateEditor(dateFormat, true ); // 第二個參數表示是否容許爲空
       binder.registerCustomEditor(Date. class , propertyEditor);
    }

    @RequestMapping ( "dataBinder/{date}" )
    public void testDate( @PathVariable Date date, Writer writer) throws IOException {
       writer.write(String.valueOf (date.getTime()));
    }

} 

 

       在上面的代碼中當咱們請求 /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 方法是用於把字符串類型的值轉換爲對應的對象的,而 getAsText 方法是用於把對象當作字符串來返回的。在 setAsText 中咱們通常先把字符串類型的對象轉爲特定的對象,而後利用 PropertyEditor 的 setValue 方法設定轉換後的值。在 getAsText 方法中通常先使用 getValue 方法取代當前的對象,而後把它轉換爲字符串後再返回給 getAsText 方法。下面是一個示例:

 

Java代碼  
    @InitBinder
    public void dataBinder(WebDataBinder binder) {
       // 定義一個 User 屬性編輯器
       PropertyEditor userEditor = new PropertyEditorSupport() {

           @Override
           public String getAsText() {
              // TODO Auto-generated method stub
              User user = (User) getValue();
              return user.getUsername();
           }

           @Override
           public void setAsText(String userStr) throws IllegalArgumentException {
              // TODO Auto-generated method stub
              User user = new User(1, userStr);
              setValue(user);
           }
       };
       // 使用 WebDataBinder 註冊 User 類型的屬性編輯器
       binder.registerCustomEditor(User. class , userEditor);
    } 

 

2. 實現 WebBindingInitializer 接口定義全局的類型轉換器

       若是須要定義全局的類型轉換器就須要實現本身的 WebBindingInitializer 對象,而後把該對象注入到AnnotationMethodHandlerAdapter 中,這樣 Spring 在遇到本身不能解析的對象的時候就會到全局的WebBindingInitializer 的 initBinder 方法中去找,每次遇到不認識的對象時, initBinder 方法都會被執行一遍。

 

Java代碼  
public class MyWebBindingInitializer implements WebBindingInitializer {

    @Override
    public void initBinder(WebDataBinder binder, WebRequest request) {
       // TODO Auto-generated method stub
       DateFormat dateFormat = new SimpleDateFormat( "yyyyMMdd" );
       PropertyEditor propertyEditor = new CustomDateEditor(dateFormat, true );
       binder.registerCustomEditor(Date. class , propertyEditor);
    }

} 

 

 

定義了這麼一個 WebBindingInitializer 對象以後 Spring 仍是不能識別其中指定的對象,這是由於咱們只是定義了 WebBindingInitializer 對象,尚未把它交給 Spring , Spring 不知道該去哪裏找解析器。要讓 Spring 可以識別還須要咱們在 SpringMVC 的配置文件中定義一個AnnotationMethodHandlerAdapter 類型的 bean 對象,而後利用本身定義的WebBindingInitializer 覆蓋它的默認屬性 webBindingInitializer 。

 

Xml代碼  
    < bean class = "org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" >
       < property name = "webBindingInitializer" >
           < bean class = "com.host.app.web.util.MyWebBindingInitializer" />
       </ property >
    </ bean > 

 

 

3.觸發數據綁定方法的時間

當Controller處理器方法參數使用@RequestParam、@PathVariable、@RequestHeader、@CookieValue和@ModelAttribute標記的時候都會觸發initBinder方法的執行,這包括使用WebBindingInitializer定義的全局方法和在Controller中使用@InitBinder標記的局部方法。並且每一個使用了這幾個註解標記的參數都會觸發一次initBinder方法的執行,這也意味着有幾個參數使用了上述註解就會觸發幾回initBinder方法的執行。

相關文章
相關標籤/搜索