Spring MVC 中“攔截器”處理模型資料 (二) @ModelAttribute
阿新 • • 發佈:2019-01-04
在這裡強烈建議看看我之前寫的幾篇關於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修飾目標方法入參。這個時候就會丟擲異常。我們知道原理之後很容易去避免這個異常。