目錄
(3)、SpringMVC確定目標方法POJO類型入參的過程
一、處理模型數據
SpringMVC提供了以下幾種途徑輸出模型數據:
ModelAndView
處理方法返回值類型爲ModelAndView時,方法體即可通過該對象添加模型數據Map及Model
入參爲org.springframeword.ui.Model
、org.springframeword.ui.ModelMap
或java.uitl.Map
時,處理方法返回時,Map中的數據會自動添加到模型中。@SessionAttributes
將模型中的某個屬性暫存到HttpSession中,以便多個請求之間可以共享這個屬性@ModelAttribute
方法入參標註該註解後,入參的對象就會放到數據模型中。
1、ModelAndView
控制器處理方法的返回值如果爲ModelAndView
,則其既包含視圖信息,也包含模型數據信息。
添加模型數據的方法
ModelAndView addObject(String attributeName,Object attributeValue);
ModelAndView addAllObject(Map <String,?> modelMap);
設置視圖的方法
void setView(View view)
void setViewName(String viewName);
SpringMVC會把ModelAndView的model中的數據放入到request域對象中,因此可以直接使用request來獲取數據
控制器類方法如下:
package com.cerr.springmvc.handlers;
import com.cerr.springmvc.entities.User;
import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;
import org.springframework.stereotype.Controller;
import org.springframework.util.concurrent.SuccessCallback;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {
private static final String SUCCESS = "success";
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView(){
String viewName = SUCCESS;
ModelAndView modelAndView = new ModelAndView(viewName);
//添加模型數據到ModelAndView中
modelAndView.addObject("time",new Date());
return modelAndView;
}
}
鏈接:<a href="springmvc/testModelAndView">testModelAndView</a>
可以直接在目標頁面來通過request獲取數據:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
time:${requestScope.time}
</body>
</html>
2、Map或Model
可以在方法中傳入一個Map或Model型參數,在方法返回時,會將Map中的數據自動添加到模型中
SpringMVC在內部使用了一個org.springframeword.ui.Model
接口存儲模型數據。
其原理具體步驟:
- SpringMVC在調用方法前會創建一個隱含的模型對象作爲模型數據的存儲容器。
- 如果方法的入參爲
Map
或Model
類型,SpringMVC會將隱含模型的引用傳遞給這些入參。在方法體內,開發者可以通過這個入參對象訪問到模型中的所有數據,也可以向模型中添加新的屬性數據。
控制器類方法如下:
package com.cerr.springmvc.handlers;
import com.cerr.springmvc.entities.User;
import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;
import org.springframework.stereotype.Controller;
import org.springframework.util.concurrent.SuccessCallback;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {
private static final String SUCCESS = "success";
@RequestMapping(value = "/testMap")
public String testMap(Map <String,Object> map){
map.put("names", Arrays.asList("Tom","Jerry","Mike"));
System.out.println(map.get("names"));
return SUCCESS;
}
}
鏈接:<a href="springmvc/testMap">testMap</a>
可以直接在目標頁面來通過request獲取數據:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
names:${requestScope.names}
</body>
</html>
3、@SessionAtttibutes
若希望在多個請求之間共用某個模型屬性數據,則可以在控制器類上標註一個@SessionAttributes
,SpringMVC會將在模型中對應的屬性暫存到HttpSession中。並且有一點需要注意的是,這個註解只能在類上面標註,而不能在方法上面標註。
@SessionAttributes除了可以通過屬性名指定需要放到會話中的屬性外,還可以通過模型屬性的對象類型指定哪些模型屬性需要放到會話中,例如:
@SessionAttributes(types=User.class)
會將隱含模型中所有類型爲User.class的屬性添加到會話中@SessionAttributes(value={"user1","user2"})
會將隱含模型中key值爲"user1"和"user2"的屬性添加到會話中@SessionAttributes(types={User.class,Dept.class})
會將隱含模型中所有類型爲User.class和Dept.class的屬性添加到會話中@SessionAttributes(value={"user1","user2"},types={Dept.class})
會將隱含模型中所有類型爲Dept.class和key值爲"user1"和"user2"的屬性添加到會話中
控制器類方法如下:
package com.cerr.springmvc.handlers;
import com.cerr.springmvc.entities.User;
import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;
import org.springframework.stereotype.Controller;
import org.springframework.util.concurrent.SuccessCallback;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
@SessionAttributes(value = {"user"},types = {String.class})
@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {
private static final String SUCCESS = "success";
@RequestMapping("/testSessionAttributes")
public String testSessionAttributes(Map<String,Object> map){
User user = new User("Tom","123456","[email protected]",15);
map.put("user",user);
map.put("school","School");
return SUCCESS;
}
}
鏈接:<a href="springmvc/testSessionAttributes">testSessionAttributes</a>
可以直接在目標頁面來通過requestScope
或sessionScope
獲取數據:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
user:${requestScope.user}
<br><br>
sessionUser:${sessionScope.user}
sessionString:${sessionScope.school}
</body>
</html>
4、@ModelAttribute
(1)、需求的解決
我們現在來做一個數據庫的模擬操作,我們現在有一條現有的記錄:
字段有id,username,password,email,age
值分別爲1,"Tom","123456","[email protected]",12
其中我們想對該記錄進行更新操作(模擬),但是其中的id和password不能被修改,我們現在有一個表單如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<!--
模型修改操作:
1.原始數據爲:1,Tom,123456,[email protected],12
2.密碼不能被修改
3.表單回顯,模擬操作直接在表單填寫對應的屬性值
-->
<form action="springmvc/testModelAttribute">
<input type="hidden" name="id" value="1"/>
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="12"><br>
<input type="submit" value="Submit">
</form>
</body>
</html>
其中的id有一個隱藏域。
如果按照平時的想法,我提交後在後端new一個對象出來,然後把表單提交的值都賦給它,這樣去做更新操作的話,則password字段會被丟失(因爲我們在表單處並沒有password)。
所以這裏要使用以下思路:
提交表單之後,後端先從數據庫中獲取該對象(此處是模擬,所以是直接new出來一個有原來數據的對象),然後再將表單的值賦值給該對象,直接使用該對象,這樣的話password字段就不會丟失了。
在此處我們先使用到 @ModelAttribute
註解,下面再給予解釋:
package com.cerr.springmvc.handlers;
import com.cerr.springmvc.entities.User;
import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;
import org.springframework.stereotype.Controller;
import org.springframework.util.concurrent.SuccessCallback;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
@RequestMapping("/springmvc")
@Controller
public class SpringMVCTest {
private static final String SUCCESS = "success";
/**
* 有@ModelAttribute標記的方法,會在每個目標方法執行之前被SpringMVC調用
* @param id
* @param map
*/
@ModelAttribute
public void getUser(@RequestParam(value = "id",required = false) Integer id,
Map<String,Object> map){
if (id != null){
User user = new User(1,"tom","123456","[email protected]",12);
System.out.println("從數據庫中獲取一個對象:"+user);
map.put("user",user);
}
}
@RequestMapping("/testModelAttribute")
public String testModelAttribute(User user){
System.out.println("修改:"+user);
return SUCCESS;
}
}
(2)、@ModelAttribute的運行原理
對於上述的控制器類中,運行流程如下:
- 執行
@ModelAttribute
註解修飾的方法,從數據庫中取出對象,把對象放入到Map中,鍵爲user - SpringMVC從Map中取出User對象,並把表單的請求參數賦給該User對象的對應屬性。
- SpringMVC把上述對象傳入目標方法的參數。
注意:在@ModelAttribute
修飾的方法中,放入到Map時的鍵需要和目標方法入參類型的第一個字母小寫的字符串一致。
- 調用
@ModelAttribute
註解修飾的方法,實際上把@ModelAttribue方法中Map中的數據放在implicitModel中。 - 解析請求處理器的目標參數,實際上該目標參數來自於WebDataBinder對象的target屬性。
1)創建WebDataBinder對象
這裏分爲兩步:
第一步:確定objectNam
e屬性,若傳入的attrName
屬性值爲"",則objectName爲類名第一個字母小寫,若目標方法的POJO屬性使用了@ModelAttribute
來修飾,則attrName
值即爲@ModelAttribute
的value屬性值;
第二步:確定target屬性,在implicitMode
l中查找attrName
對應的屬性值,若存在,則賦值;若不存在,則驗證當前Handler
是否使用了@SessionAttributes
進行修飾,若使用了,則嘗試從Session中獲取attrName
所對應的屬性值,若session中沒有對應的屬性值,則會拋出一個異常。若Handler
沒有使用@SessionAttributes
進行修飾,或@SessionAttributes中沒使用value值指定的鍵和attrName相互匹配,則通過反射創建了對應的POJO對象。
2)SpringMVC把表單的請求參數賦給了WebDataBinder的target對應的屬性。
3)SpringMVC會把WebDataBinder的attrName和target給到implicitModel,進而傳到request域對象中
4)把WebDataBinder的target作爲參數傳遞給目標方法的入參。
(3)、SpringMVC確定目標方法POJO類型入參的過程
-
確定一個key
若目標方法的POJO類型的參數沒有使用@ModelAttribute
作爲修飾,則key爲POJO類名第一個字母的小寫;若使用了@ModelAttribute
來修飾,則key爲@ModelAttribute
註解的value屬性值。 -
在implicitModel中查找key對應的對象,若存在,則作爲入參傳入
若在ModelAttribute
標記的方法中在Map中保存過,且該key與第一步中確定的key一直,則會獲取到。 -
若implicitModel中不存在key對應的對象,則檢查當前的Handler是否使用
@SessionAttributes
註解修飾。
若使用了該註解,且@SessionAttributes
註解的value屬性值中包含了key,則從HttpSession中來獲取key所對應的value值,若存在則直接傳入到目標方法的入參中,若不存在則拋出異常。 -
若
Handler
沒有標識@SessionAttributes
註解或@SessionAttributes
註解的value值不包含key,則會通過反射來創建POJO類型的參數,傳入爲目標方法的參數。 -
SpringMVC會把key和value傳入到implicitModel中,進而會保存到
request
中。
(4)、關於@ModelAttribute的兩種用法
-
有
@ModelAttribute
標記的方法,會在每個目標方法執行之前被SpringMVC調用 -
@ModelAttribute
註解也可以來修飾目標方法POJO類型的入參,其value屬性值有如下的作用:
(1)SpringMVC會使用value屬性值在implicitModel中查找對應的對象,若存在則會直接傳入到目標方法的入參中
對於上面需求解決的控制器代碼,在@ModelAttribute
標註的方法中我們在添加模型數據的時候給user對象的key值爲入參類型的第一個字母小寫,而我們現在想將該名字取爲任意名字,那麼可以這樣操作,第一步是將@ModelAttribute
標註的方法代碼中在模型中添加數據時的key名修改。
@ModelAttribute
public void getUser(@RequestParam(value = "id",required = false) Integer id,
Map<String,Object> map){
if (id != null){
User user = new User(1,"tom","123456","[email protected]",12);
System.out.println("從數據庫中獲取一個對象:"+user);
//不再使用入參類型的第一個字母的小寫作爲key值
map.put("abc",user);
}
}
第二步是在我們需要使用到該數據的方法的入參中添加@ModelAttribute
註解標註POJO類型入參的名字,例如此處的入參應寫爲@ModelAttribute(value = "abc") User user
:
@RequestMapping("/testModelAttribute")
public String testModelAttribute(@ModelAttribute(value = "abc") User user){
System.out.println("修改:"+user);
return SUCCESS;
}
(2)SpringMVC會以value爲key,POJO類型的對象爲value,存入到request中。
對於value,如果使用了(1),則value就是@ModelAttribute
的value;如果沒有使用該註解,則value就是入參類型的第一個字母小寫。