1. 程式人生 > >Spring MVC 中“攔截器”處理模型資料 (二) @ModelAttribute

Spring MVC 中“攔截器”處理模型資料 (二) @ModelAttribute

在這裡強烈建議看看我之前寫的幾篇關於SpringMVC的部落格,都是串通的。

@ModelAttribute這個是SpringMVC中處理模型資料的最難也是最重要的點。相當於以前Struct的攔截器。

用途:比如我們要修改一個物件的部分資料,按照以前的思維,new一個物件儲存資料,然後賦值,把不修改資料先拿出來儲存起來。但是這個已經Out了, 在SpringMVC中,是拿到資料庫的例項,然後把傳進來的值也就是需要修改的值set進去,那麼沒有set的值即為不需要修改的值。

index.jsp

<!-- 模擬修改操作
        1. 原始資料 : 1, yexx, 123456, 
[email protected]
, 12 2. 密碼不能被修改 3. 表單回顯, 模擬操作直接在表單填寫對應的屬性值 -->
<form action="springmvc/testModelAttributes" method="post"> <input type="hidden" name="id" value="1"/> username: <input type="text" name="username" value="yexx"/> <br
/>
email: <input type="text" name="email" value="[email protected]"/> <br/> age: <input type="text" name="age" value="13"/> <br/> <input type="submit" value="Submit"/> </form>
package com.hust.springmvc1;

import java.util.Map;
import
org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import com.hust.springmvc.entities.User; //@SessionAttributes(value={"user"}, types={String.class}) @Controller @RequestMapping("/springmvc") public class SpringMVCTest { private static final String SUCCESS = "success"; /** * 1. 由@ModelAttribute 標記的方法,會在每個目標方法執行之前被SpringMVC呼叫! * 2. @ModelAttribute 註解也可以來修飾目標方法POJO 型別的入參,且value 屬性值如下的作用: * 1). SpringMVC 會使用value 屬性值 在implicitModel 中查詢對應的物件,若存在則會直接傳入到目標方法的入參中。 * 2). SpringMVC 會一value 為 key , POJO 型別的物件為value, 存入request 中。 */ @ModelAttribute public void getUser(@RequestParam(value="id", required=false) Integer id, Map<String, Object> map) { System.out.println("ModelAttribute"); if (id!=null) { //模擬從資料庫中獲取物件 User user = new User(1, "yexx", "123456", "[email protected]", 12); System.out.println("從資料庫中獲取一個物件:" + user); map.put("user", user); } } /** * 執行流程: * 1. 執行@ModelAttribute 註解修飾的方法:從資料庫中取出物件,把物件放入到Map中。鍵為:user * 2. SpringMVC 從 Map 中取出user物件, 並把表單的請求引數賦給該User 物件對應的屬性。 * 3. SpringMVC 把上述物件傳入目標方法的引數。 * * 注意: 在@ModelAttribute 修飾的方法中,放入到Map時的鍵需要和目標方法入參型別的第一個字母小寫的字串一致 * * SpringMVC 確定目標方法POJO 型別入參的過程 * 1. 確定一個key: * 1). 若目標方法的POJO型別的引數木有使用@ModelAttribute 作為修飾,則key 為 POJO類名第一個字母的小寫 * 2). 若使用了@ModelAttribute 來修飾, 則key 為@ModelAttribute 註解的value屬性值。 * 2. 在 implicitModel 中查詢 key 對應的物件, 若存在, 則作為入參傳入 * 1). 若在@ModelAttribute 標記的方法中在 Map 中儲存過, 且 key 和 1 確定的key 一致,則會獲取到。 * 3. 若 implicitModel 中不存在key 對應的物件, 則檢查當前的 Handler 是否使用了@SessionAttributes 註解修飾, * 若使用了該註解, 且 @SessionAttributes 註解的 value 屬性值中包含了key, 則會從HttpSession 中來獲取 key 所對應 * 的value 的值, 若存在則直接傳入到目標方法的入參中, 若不存在則將丟擲異常。 * 4. 若Handler 沒有表示@SessionAttributes 註解或 @SessionAttributes 註解的 value 值中不包含 key, 則會 * 通過反射來建立 POJO型別的引數, 傳入為目標方法的引數 * 5. SpringMVC 會把key 和 POJO 型別的物件 儲存到implicitModel 中, 進而會儲存到 request 中。 * * 原始碼分析的流程 * 1. 呼叫@ModelAttribute 註解修飾的方法。 實際上把@ModelAttribute 方法中Map 中的資料放在了 implicitModel中。 * 2. 解析請求處理器的目標引數,實際上該目標引數來自於 WebDataBinder 物件的 target屬性 * 1). 建立WebDataBinder 物件 * ① 確定objectName 屬性: 若傳入的attrName 屬性值為空,則objectName 為類名第一個字母小寫。 * *注意: attrName 若目標方法的POJO屬性使用了@ModelAttribute 來修飾,則attrName 值即為 @ModelAttribute的value值 * ② 確定target 屬性: * > 在implicitModel 中查詢 attrName 對應的屬性值。 若存在。 OK * > 若不存在: 則驗證當前Handler 是否使用了@sessionAttributes 進行修飾,若使用了,則嘗試從Session中獲取attrName 所對應 * 的屬性值。 若session 中沒有對應的屬性值,則丟擲異常 * > 若Handler 沒有使用@SessionAttributes 進行修飾, 或@SessionAttribute 中沒有使用 value 值制定的 key * 和 attrName 相匹配,則通過反射建立POJO物件 * * 2). SpringMVC 把表單的請求引數賦給了WebDataBinder 的target 屬性 * 3). *SpringMVC 會把WebDataBinder 的attrName 和 target 給到 implicitModel * 4). 把WebDataBinder 的 target 作為引數傳遞給目標方法入參。 */ @RequestMapping("/testModelAttributes") public String testModelAttributes(@ModelAttribute("user") User user) { System.out.println("update:" + user); return SUCCESS; } }

這段程式的執行結果就會是:
這裡寫圖片描述

該回顯資料也是能夠顯示出來。我把執行過程和原始碼分析拿出來特別的說一下。

執行流程:

 * 1. 執行@ModelAttribute 註解修飾的方法:從資料庫中取出物件,把物件放入到Map中。鍵為:user
 * 2. SpringMVC 從 Map 中取出user物件, 並把表單的請求引數賦給該User 物件對應的屬性。
 * 3. SpringMVC 把上述物件傳入目標方法的引數。
 * 
 * 注意: 在@ModelAttribute  修飾的方法中,放入到Map時的鍵需要和目標方法入參型別的第一個字母小寫的字串一致。

原始碼分析的流程

 * 1. 呼叫@ModelAttribute 註解修飾的方法。 實際上把@ModelAttribute 方法中Map 中的資料放在了 implicitModel中。
 * 2. 解析請求處理器的目標引數,實際上該目標引數來自於 WebDataBinder 物件的 target屬性
 * 1). 建立WebDataBinder 物件
 * ① 確定objectName 屬性: 若傳入的attrName 屬性值為空,則objectName 為類名第一個字母小寫。
 * *注意: attrName 若目標方法的POJO屬性使用了@ModelAttribute 來修飾,則attrName 值即為 @ModelAttribute的value值
 * ② 確定target 屬性:
 *  > 在implicitModel 中查詢 attrName 對應的屬性值。 若存在。 OK
 *  > 若不存在: 則驗證當前Handler 是否使用了@sessionAttributes 進行修飾,若使用了,則嘗試從Session中獲取attrName 所對應
 *  的屬性值。 若session 中沒有對應的屬性值,則丟擲異常
 *  > 若Handler 沒有使用@SessionAttributes 進行修飾, 或@SessionAttribute 中沒有使用 value 值制定的 key
 *  和 attrName 相匹配,則通過反射建立POJO物件
 *  
 *  2). SpringMVC 把表單的請求引數賦給了WebDataBinder 的target 屬性
 *  3). *SpringMVC 會把WebDataBinder 的attrName 和 target 給到 implicitModel
 *  4). 把WebDataBinder 的 target 作為引數傳遞給目標方法入參。 

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

 * 1. 確定一個key:
 * 1). 若目標方法的POJO型別的引數木有使用@ModelAttribute 作為修飾,則key 為 POJO類名第一個字母的小寫
 * 2). 若使用了@ModelAttribute 來修飾, 則key 為@ModelAttribute 註解的value屬性值。
 * 2. 在 implicitModel 中查詢 key 對應的物件, 若存在, 則作為入參傳入
 * 1). 若在@ModelAttribute 標記的方法中在 Map 中儲存過, 且 key 和 1 確定的key 一致,則會獲取到。
 * 3. 若 implicitModel 中不存在key 對應的物件, 則檢查當前的 Handler 是否使用了@SessionAttributes 註解修飾,
 * 若使用了該註解, 且 @SessionAttributes 註解的 value 屬性值中包含了key, 則會從HttpSession 中來獲取 key 所對應
 * 的value 的值, 若存在則直接傳入到目標方法的入參中, 若不存在則將丟擲異常。
 * 4. 若Handler 沒有表示@SessionAttributes 註解或 @SessionAttributes 註解的 value 值中不包含 key, 則會
 * 通過反射來建立 POJO型別的引數, 傳入為目標方法的引數
 * 5. SpringMVC 會把key 和 POJO 型別的物件 儲存到implicitModel 中, 進而會儲存到 request 中。

總結

* 1. 由@ModelAttribute 標記的方法,會在每個目標方法執行之前被SpringMVC呼叫!
 * 2. @ModelAttribute 註解也可以來修飾目標方法POJO 型別的入參,且value 屬性值如下的作用:
 * 1). SpringMVC 會使用value 屬性值 在implicitModel 中查詢對應的物件,若存在則會直接傳入到目標方法的入參中。
 * 2). SpringMVC 會一value 為 key , POJO 型別的物件為value, 存入request 中。

這裡有一個特別值得注意的地方

這裡寫圖片描述
如果你的Controller被@SessionAttributes修飾了,而且value也是那個,而且沒用@ModelAttribute修飾方法,同時也沒有@ModelAttribute修飾目標方法入參。這個時候就會丟擲異常。我們知道原理之後很容易去避免這個異常。

這裡寫圖片描述