1. 程式人生 > >@ModelAttribute執行原理與引數解析

@ModelAttribute執行原理與引數解析

該篇簡要講述示例。

需求如下,當更新一個物件時,某個欄位比如密碼不能被修改。

常見解決思路有二:

① new 一個物件,form表單中密碼域為隱藏域;該種方法有風險。

② new 一個物件,在更新的時候再次從資料庫查詢密碼從而進行更新;該方法比較麻煩。

SpringMVC的解決思路:

使用@ModelAttribute !

  • JSP頁面:
    <form action="springmvc/testModelAttribute" method="Post">
        <input type="hidden" name="id" value="1"
/> //注意此處隱藏域為id username: <input type="text" name="username" value="Tom"/> <br> email: <input type="text" name="email" value="[email protected]"/> <br> age: <input type="text" name="age" value="18"/> <br> <input type
=
"submit" value="Submit"/> </form>
  • 後臺code:
    //該方法模擬根據id從資料庫獲取物件。
    @ModelAttribute
    public void getUser(@RequestParam(value="id",required=false) Integer id, Map<String, Object> map){
        System.out.println("modelAttribute method execute...");
        if(id != null){
            //模擬從資料庫中獲取物件
User user = new User(1, "Tom", "123456", "[email protected]", 12); System.out.println("從資料庫中獲取一個物件: " + user); map.put("user", user); } } //根據表單name屬性值對model中的user物件進行更新。因為表單域沒有密碼,故密碼使用資料庫查詢得到的。 @RequestMapping("/testModelAttribute") public String testModelAttribute(@ModelAttribute("user") User user){ System.out.println("修改: " + user); return SUCCESS; }
  • result as follows:
從資料庫中獲取一個物件: User [username=Tom, password=123456, email=tom@baidu.com, age=12, address=null]

修改: User [username=Tom, password=123456, email=tom@baidu.com, age=18, address=null]

對比可知,age進行了更新 ,password延用了資料庫查詢的資料!

Tips:

map中最終放的user並非資料庫查詢得到,而是經過了表單更新後的user !

這裡寫圖片描述

以下為執行原理分析:

  • 1)執行 @ModelAttribute 註解修飾的方法(注意是方法,而不是引數): 從資料庫中取出物件, 把物件放入到了 Map 中. 鍵為: user;

    即,在執行目標方法前,會先判斷controller中有沒有@ModelAttribute註解的方法(注意不是引數)。如果有,則先執行該方法。**

  • 2)SpringMVC 從 Map 中取出 User 物件, 並把表單的請求引數賦給該 User 物件的對應屬性.

    判斷modelMap中有沒有方法引數user對應的屬性物件,即,modelMap中有沒有{user:object}。如果有,取出使用;如果無,通過反射new 一個user物件出來。

  • 3)SpringMVC 把上述物件傳入目標方法的引數(最終匹配url的方法).

注意: 在 @ModelAttribute 修飾的方法中,放入到 Map 時的鍵需要和目標方法入參型別的第一個字母小寫的字串一致!(User user)map.put(“user”, user);如果不一致,需要@ModelAttribute()顯示指定!!

【SpringMVC 確定目標方法 POJO 型別入參的過程】

①. 確定一個 key:

1). 若目標方法的 POJO 型別的引數沒有使用 @ModelAttribute 作為修飾, 則 key 為 POJO 類名第一個字母的小寫

2). 若使用了 @ModelAttribute 來修飾, 則 key 為 @ModelAttribute 註解的 value 屬性值。

下面確定key對應的value。

②. 在 implicitModel 中查詢 key 對應的物件, 若存在, 則作為入參傳入

若在 @ModelAttribute 標記的方法中在 Map 中儲存過, 且 key 和 ①確定的 key 一致, 則會獲取到.

③. 若 implicitModel 中不存在 key 對應的物件, 則檢查當前的 Handler 是否使用 @SessionAttributes 註解修飾,

若使用了該註解, 且 @SessionAttributes 註解的 value 屬性值中包含了 key, 則會從 HttpSession 中來獲取 key 所 對應的 value 值。

若value存在則直接傳入到target(target是什麼,繼續往下看)中;若不存在則將丟擲異常.

④若 Handler 沒有標識 @SessionAttributes 註解或 @SessionAttributes 註解的 value 值中不包含 key, 則 會通過反射來建立 POJO 型別的引數, 傳入為目標方法的引數

⑤. SpringMVC 會把 key 和 POJO 型別的物件儲存到 implicitModel 中, 進而會儲存到 request 中(先儲存再傳給目標方法引數,具體看原始碼分析流程).

  • 該步結束後,request中會存在(key : POJO)物件

【原始碼分析的流程】

綜合來講有如下兩個過程:

①. 呼叫 @ModelAttribute 註解修飾的方法(註解在方法上)。

  • 實際上把 @ModelAttribute 方法中 Map 中的資料放在了 implicitModel 中.

②. 解析請求處理器的目標引數, 實際上該目標引數來自於 WebDataBinder 物件的 target 屬性

詳細過程如下:

1). 建立 WebDataBinder 物件:

①.確定 objectName 屬性: 若傳入的 attrName 屬性值為 “”, 則 objectName 為類名第一個字母小寫(如user);

注意: attrName. 若目標方法的 POJO 屬性使用了 @ModelAttribute 來修飾, 則 attrName 值即為 @ModelAttribute 的 value 屬性值 (也可以為abc)

②. 確定 target 屬性( 即從request或sessionAttribute中根據key獲取的物件或者根據目標型別反射新建的空的物件):

在 implicitModel 中查詢 attrName 對應的屬性值;若存在, ok( map( attrName:user))(預設為請求域,可以理解為如果請求域中沒有則去session域中尋找)

若不存在: 則驗證當前 Handler 是否使用了 @SessionAttributes 進行修飾, 若使用了, 則嘗試從 Session 中 獲取 attrName 所對應的屬性值 ; 若 session 中沒有對應的屬性值, 則丟擲了異常。

若 Handler 沒有使用 @SessionAttributes 進行修飾, 或 @SessionAttributes 中沒有 value 屬性值指定的 key和 attrName 相匹配, 則通過反射建立了 POJO 物件 (此時 map(value:new POJO ))

2).SpringMVC 把表單的請求引數賦給了 WebDataBinder 的 target物件對應的屬性。

如果target物件是從request或者session域中獲取的,那麼把表單引數再次賦值給該物件,將會改變物件的值!

3). SpringMVC 會把 WebDataBinder 的 attrName 和 target 給到 implicitModel, 進而傳到 request 域物件中。

4). 把 WebDataBinder 的 target 作為引數傳遞給目標方法的入參。

綜上,SpringMVC中,方法入參的繫結離不開WebDataBinder物件。其實,只從名字就可以得知,該物件的作用就是將web頁面請求傳過來的引數賦予後臺方法。