SpringMVC學習筆記 | SpringMVC中處理模型數據的幾種方法(ModelAndView|@SessionAttributes|@ModelAttribute)以及運行原理...

目錄

一、處理模型數據

1、ModelAndView

2、Map或Model

3、@SessionAtttibutes

4、@ModelAttribute

(1)、需求的解決

(2)、@ModelAttribute的運行原理

(3)、SpringMVC確定目標方法POJO類型入參的過程

(4)、關於@ModelAttribute的兩種用法


一、處理模型數據

SpringMVC提供了以下幾種途徑輸出模型數據:

  • ModelAndView
    處理方法返回值類型爲ModelAndView時,方法體即可通過該對象添加模型數據
  • Map及Model
    入參爲org.springframeword.ui.Modelorg.springframeword.ui.ModelMapjava.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在調用方法前會創建一個隱含的模型對象作爲模型數據的存儲容器。
  • 如果方法的入參爲MapModel類型,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>

可以直接在目標頁面來通過requestScopesessionScope獲取數據:

<%@ 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對象
    這裏分爲兩步:
    第一步:確定objectName屬性,若傳入的attrName屬性值爲"",則objectName爲類名第一個字母小寫,若目標方法的POJO屬性使用了@ModelAttribute來修飾,則attrName值即爲@ModelAttribute的value屬性值
    第二步:確定target屬性,在implicitModel中查找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就是入參類型的第一個字母小寫。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章