@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頁面請求傳過來的引數賦予後臺方法。