1. 程式人生 > >SpringMvc之@RequestParam詳解

SpringMvc之@RequestParam詳解

@RequestParam是傳遞引數的.

@RequestParam用於將請求引數區資料對映到功能處理方法的引數上。

public String queryUserName(@RequestParam String userName)

在url中輸入:localhost:8080/**/?userName=zhangsan

請求中包含username引數(如/requestparam1?userName=zhang),則自動傳入。

接下來我們看一下@RequestParam註解主要有哪些引數:

value:引數名字,即入參的請求引數名字,如username表示請求的引數區中的名字為username的引數的值將傳入;

required:是否必須,預設是true,表示請求中一定要有相應的引數,否則將報404錯誤碼;

defaultValue:預設值,表示如果請求中沒有同名引數時的預設值,預設值可以是SpEL表示式,如“#{systemProperties['java.vm.version']}”。

表示請求中可以沒有名字為username的引數,如果沒有預設為null,此處需要注意如下幾點:

public String queryUserName(@RequestParam(value="userName" ,required =false ) String userName)

     原子型別:必須有值,否則丟擲異常,如果允許空值請使用包裝類代替。

     Boolean包裝型別型別:預設Boolean.FALSE,其他引用型別預設為null。

public String requestparam5(  
@RequestParam(value="username", required=true, defaultValue="zhang") String username)   
          

如果沒有傳入引數,則預設是"zhangsan".

但是在傳遞引數的時候如果是url?userName=zhangsan&userName=wangwu時怎麼辦呢?

其實在實際roleList引數入參的資料為“zhangsan,wangwu”,即多個數據之間使用“,”分割;我們應該使用如下方式來接收多個請求引數:

複製程式碼

public String requestparam8(@RequestParam(value="userName") String []  userNames)     
或者是:
public String requestparam8(@RequestParam(value="list") List<String> list)     

複製程式碼

@PathVariable繫結URI模板變數值

@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。程式碼在PathVariableTypeController中

6.6.4、@CookieValue繫結Cookie資料值

@CookieValue用於將請求的Cookie資料對映到功能處理方法的引數上。

public String test(@CookieValue(value="JSESSIONID", defaultValue="") String sessionId) 

如上配置將自動將JSESSIONID值入參到sessionId引數上,defaultValue表示Cookie中沒有JSESSIONID時預設為空。

public String test2(@CookieValue(value="JSESSIONID", defaultValue="") Cookie sessionId)     

傳入引數型別也可以是javax.servlet.http.Cookie型別

測試程式碼在CookieValueTypeController中。@CookieValue也擁有和@RequestParam相同的三個引數,含義一樣。

6.6.5、@RequestHeader繫結請求頭資料

@RequestHeader用於將請求的頭資訊區資料對映到功能處理方法的引數上。

@RequestMapping(value="/header")  
public String test(  
       @RequestHeader("User-Agent") String userAgent,  
       @RequestHeader(value="Accept") String[] accepts)

如上配置將自動將請求頭“User-Agent”值入參到userAgent引數上,並將“Accept”請求頭值入參到accepts引數上。測試程式碼在HeaderValueTypeController中。

@RequestHeader也擁有和@RequestParam相同的三個引數,含義一樣。

6.6.6、@ModelAttribute繫結請求引數到命令物件

@ModelAttribute一個具有如下三個作用:

①繫結請求引數到命令物件:放在功能處理方法的入參上時,用於將多個請求引數繫結到一個命令物件,從而簡化繫結流程,而且自動暴露為模型資料用於檢視頁面展示時使用;

②暴露表單引用物件為模型資料:放在處理器的一般方法(非功能處理方法)上時,是為表單準備要展示的表單引用物件,如註冊時需要選擇的所在城市等,而且在執行功能處理方法(@RequestMapping註解的方法)之前,自動新增到模型物件中,用於檢視頁面展示時使用;

③暴露@RequestMapping方法返回值為模型資料:放在功能處理方法的返回值上時,是暴露功能處理方法的返回值為模型資料,用於檢視頁面展示時使用。

一、繫結請求引數到命令物件

如使用者登入,我們需要捕獲使用者登入的請求引數(使用者名稱、密碼)並封裝為使用者物件,此時我們可以使用@ModelAttribute繫結多個請求引數到我們的命令物件。

  1. public String test1(@ModelAttribute("user") UserModel user)  

和6.6.1一節中的五、命令/表單物件功能一樣。只是此處多了一個註解@ModelAttribute("user"),它的作用是將該繫結的命令物件以“user”為名稱新增到模型物件中供檢視頁面展示使用。我們此時可以在檢視頁面使用${user.username}來獲取繫結的命令物件的屬性。

繫結請求引數到命令物件支援物件圖導航式的繫結,如請求引數包含“?username=zhang&password=123&workInfo.city=bj”自動繫結到user中的workInfo屬性的city屬性中。

Java程式碼  

  1. @RequestMapping(value="/model2/{username}")  
    public String test2(@ModelAttribute("model") DataBinderTestModel model) {   

DataBinderTestModel相關模型請從第三章拷貝過來,請求引數到命令物件的繫結規則詳見【4.16.1、資料繫結】一節,URI模板變數也能自動繫結到命令物件中,當你請求的URL中包含“bool=yes&schooInfo.specialty=computer&hobbyList[0]=program&hobbyList[1]=music&map[key1]=value1&map[key2]=value2&state=blocked”會自動繫結到命令物件上。

當URI模板變數和請求引數同名時,URI模板變數具有高優先權。

二、暴露表單引用物件為模型資料

Java程式碼  

  1. @ModelAttribute("cityList")  
    public List<String> cityList() {  
        return Arrays.asList("北京", "山東");  
    } 
      

如上程式碼會在執行功能處理方法之前執行,並將其自動新增到模型物件中,在功能處理方法中呼叫Model 入參的containsAttribute("cityList")將會返回true。

Java程式碼  

  1. 複製程式碼

    @ModelAttribute("user")  //①  
    public UserModel getUser(@RequestParam(value="username", defaultValue="") String username) {  
    //TODO 去資料庫根據使用者名稱查詢使用者物件  
    UserModel user = new UserModel();  
    user.setRealname("zhang");  
         return user;  
    }  

    複製程式碼

如你要修改使用者資料時一般需要根據使用者的編號/使用者名稱查詢使用者來進行編輯,此時可以通過如上程式碼查詢要編輯的使用者。

也可以進行一些預設值的處理。

Java程式碼  

  1. @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方法返回值為模型資料

Java程式碼  

  1. public @ModelAttribute("user2") UserModel test3(@ModelAttribute("user2") UserModel user)  

大家可以看到返回值型別是命令物件型別,而且通過@ModelAttribute("user2")註解,此時會暴露返回值到模型資料(名字為user2)中供檢視展示使用。那哪個檢視應該展示呢?此時Spring Web MVC會根據RequestToViewNameTranslator進行邏輯檢視名的翻譯,詳見【4.15.5、RequestToViewNameTranslator】一節。

此時又有問題了,@RequestMapping註解方法的入參user暴露到模型資料中的名字也是user2,其實我們能猜到:

(3、@ModelAttribute註解的返回值會覆蓋@RequestMapping註解方法中的@ModelAttribute註解的同名命令物件。

四、匿名繫結命令引數

Java程式碼  

  1. public String test4(@ModelAttribute UserModel user, Model model)  
    或  
    public String test5(UserModel user, Model model)   

此時我們沒有為命令物件提供暴露到模型資料中的名字,此時的名字是什麼呢?Spring Web MVC自動將簡單類名(首字母小寫)作為名字暴露,如“cn.javass.chapter6.model.UserModel”暴露的名字為“userModel”。

Java程式碼  

  1. public @ModelAttribute List<String> test6()  
    或  
    public @ModelAttribute List<UserModel> test7()   

對於集合型別(Collection介面的實現者們,包括陣列),生成的模型物件屬性名為“簡單類名(首字母小寫)”+“List”,如List<String>生成的模型物件屬性名為“stringList”,List<UserModel>生成的模型物件屬性名為“userModelList”。

其他情況一律都是使用簡單類名(首字母小寫)作為模型物件屬性名,如Map<String, UserModel>型別的模型物件屬性名為“map”。

6.6.7、@SessionAttributes繫結命令物件到session

有時候我們需要在多次請求之間保持資料,一般情況需要我們明確的呼叫HttpSession的API來存取會話資料,如多步驟提交的表單。Spring Web MVC提供了@SessionAttributes進行請求間透明的存取會話資料。

Java程式碼  

  1. 複製程式碼

    //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";  
    }   

    複製程式碼

@SessionAttributes(value = {"user"})含義:

@SessionAttributes(value = {"user"}) 標識將模型資料中的名字為“user” 的物件儲存到會話中(預設HttpSession),此處value指定將模型資料中的哪些資料(名字進行匹配)儲存到會話中,此外還有一個types屬性表示模型資料中的哪些型別的物件儲存到會話範圍內,如果同時指定value和types屬性則那些名字和型別都匹配的物件才能儲存到會話範圍內。

包含@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功能處理方法執行完畢會進行清理會話資料。

我們通過Spring Web MVC的原始碼驗證一下吧,此處我們分析的是Spring3.1的RequestMappingHandlerAdapter,讀者可以自行驗證Spring3.0的AnnotationMethodHandlerAdapter,流程一樣:

(1、RequestMappingHandlerAdapter.invokeHandlerMethod

Java程式碼  

  1. //1、RequestMappingHandlerAdapter首先呼叫ModelFactory的initModel方法準備模型資料:  
    modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);  
    //2、呼叫@RequestMapping註解的功能處理方法  
    requestMappingMethod.invokeAndHandle(webRequest, mavContainer);  
    //3、更新/合併模型資料  
    modelFactory.updateModel(webRequest, mavContainer);   

(2、ModelFactory.initModel

Java程式碼  

  1. 複製程式碼

    Map<String, ?> attributesInSession = this.sessionAttributesHandler.retrieveAttributes(request);  
    //1.1、將與@SessionAttributes註解相關的會話物件放入模型資料中  
    mavContainer.mergeAttributes(attributesInSession);  
    //1.2、呼叫@ModelAttribute方法新增表單引用物件  
    invokeModelAttributeMethods(request, mavContainer);  
    //1.3、驗證模型資料中是否包含@SessionAttributes註解相關的會話物件,不包含丟擲異常  
    for (String name : findSessionAttributeArguments(handlerMethod)) {  
        if (!mavContainer.containsAttribute(name)) {  
            //1.4、此處防止在@ModelAttribute註解方法又添加了會話物件  
            //如在@ModelAttribute註解方法呼叫session.setAttribute("user", new UserModel());  
            Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);  
            if (value == null) {  
                throw new HttpSessionRequiredException("Expected session attribute '" + name + "'");  
            }  
            mavContainer.addAttribute(name, value);  
    }   

    複製程式碼

(3、ModelFactory.invokeModelAttributeMethods

Java程式碼  

  1. 複製程式碼

    for (InvocableHandlerMethod attrMethod : this.attributeMethods) {  
        String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).value();   
        //1.2.1、如果模型資料中包含同名數據則不再新增  
        if (mavContainer.containsAttribute(modelName)) {  
            continue;  
        }  
        //1.2.2、呼叫@ModelAttribute註解方法並將返回值新增到模型資料中,此處省略實現程式碼  
    }   

    複製程式碼

(4、requestMappingMethod.invokeAndHandle 呼叫功能處理方法,此處省略

(5、ModelFactory.updateMode 更新模型資料

Java程式碼  

  1. 複製程式碼

    //3.1、如果會話被標識為完成,此時從會話中清除@SessionAttributes註解相關的會話物件  
    if (mavContainer.getSessionStatus().isComplete()){   
        this.sessionAttributesHandler.cleanupAttributes(request);  
    }  
    //3.2、如果會話沒有完成,將模型資料中的@SessionAttributes註解相關的物件新增到會話中  
    else {  
        this.sessionAttributesHandler.storeAttributes(request, mavContainer.getModel());  
    }  
    //省略部分程式碼  

    複製程式碼

到此@SessionAtrribute介紹完畢,測試程式碼在cn.javass.chapter6.web.controller.paramtype.SessionAttributeController中。

另外cn.javass.chapter6.web.controller.paramtype.WizardFormController是一個類似於【4.11、AbstractWizardFormController】中介紹的多步驟表單實現,此處不再貼程式碼,多步驟提交表單需要考慮會話超時問題,這種方式可能對使用者不太友好,我們可以採取隱藏表單(即當前步驟將其他步驟的表單隱藏)或表單資料存資料庫(每步驟更新下資料庫資料)等方案解決。

6.6.8、@Value繫結SpEL表示式

@Value用於將一個SpEL表示式結果對映到到功能處理方法的引數上。

Java程式碼  

  1. public String test(@Value("#{systemProperties['java.vm.version']}") String jvmVersion)