Spring MVC系列之模型綁定(SpringBoot)(七)


前言

上一節我們在SpringBoot中啟用了Spring MVC最終輸出了HelloWorld,本節我們來講講Spring MVC中的模型綁定,這個名稱來源於.NET或.NET Core,不知是否恰當,我們暫且這樣理解吧。

@RequestParam VS  @PathVariable

 一看注解名稱應該非常好理解,注解@RequestParam主要用來獲取查詢字符串參數,而注解@PathVaruable用於獲取路由參數,下面我們來看如下一個例子:

    @ResponseBody
    @RequestMapping(value = "/demo1", method = RequestMethod.GET)
    public String demo1(@RequestParam(value = "param1", required = true, defaultValue = "jeffcky") String param1,
                        @RequestParam(value = "param2", required = false) String param2) {
        return param1 + "," + param2;
    }

 

如上我們獲取查詢字符串參數param1和param2,同時呢,我們要求參數param1必須提供,若為空,我們給定默認值為jeffcky,而參數param2可不提供,則為其默認值,比如如下:

我們知道無論是注解@RequestParam還是注解@PathVariable,都有屬性required,若為false,則此參數無需提供,難道事實真的如此嗎,我們看看如下示例:

    @ResponseBody
    @RequestMapping(value = "/demo3/{id}", method = RequestMethod.GET)
    public String demo3(@RequestParam(value = "id") String param1, @PathVariable(value = "id", required = false) String param2) {
        return param1 + "," + param2;
    }

我們設置了路由上的變量id為可選,當我們請求時我們也並未提供該參數,但是結果卻是404,這也證明:注解@RequestParam獲取查詢字符串,而注解@PathVariable獲取路由參數,雖然二者注解提供參數(required)可選,但是針對注解@PathVariable該參數無效,而且參數必須提供,否則返回404。

深入探討注解@RequestParam

上述是我們針對路由和查詢字符串注解的對比,接下來我們來看看對於查詢字符串注解各種姿勢,看看Spring是如何進行處理的呢,比如我們有兩個根據注解@RequestParam的請求參數和方法一樣,此時將會發生什么呢?,如下:

    @RequestMapping(value = "/user", method = RequestMethod.GET)
    @ResponseBody
    public String say() {
        return "hello world";
    }

    @RequestMapping(value = "/user", method = RequestMethod.GET)
    public ModelAndView user() {
        User user = new User();
        user.setGender("M");
        ModelAndView modelAndView = new ModelAndView("user", "command", user);
        return modelAndView;
    }

很顯然會啟動程序后會拋出上述異常,意為不明確有兩個相同的映射,那么要是我們將say方法的請求方法給去掉,此時將表明可此方法的請求不受限制,如此這樣會報錯嗎?如若不報錯,那么會首先匹配到哪個呢?

    @RequestMapping(value = "/user")
    @ResponseBody
    public String say() {
        return "hello world";
    }

    @RequestMapping(value = "/user", method = RequestMethod.GET)
    public ModelAndView user() {
        User user = new User();
        user.setGender("M");
        ModelAndView modelAndView = new ModelAndView("user", "command", user);
        return modelAndView;
    }

由此我們可以知道:注解@RequestParam用於查詢字符串,若請求參數一致, 但一個請求方法未指定(接收所有方法),一個指定對應請求方法,結果將匹配到指定對應的請求方法。那么要是我們想讓其匹配到say方法,我們應該腫么辦呢?注解@RequestParam的參數為數組,接下來我們將say方法進行如下修改即可(參數順序可顛倒):

    @RequestMapping(value = {"/user", "*"})
    @ResponseBody
    public String say() {
        return "hello world";
    }

@ModelAttribute VS ModelMap VS ModelAndView

既然涉及到參數綁定,那么我們就得學習Spring MVC表單提交,順着這個思路我們來學習Spring MVC表單相關內容,如上圖其實我已經給大家展示了對應方法和視圖,接下來我們來通過表單提交來講講三者的區別,為了有些童鞋可能需要親自動手實踐,這里我們先給出整個結構,如下:

我們創建如下進行表單提交的用戶實體類

package com.demo.springboot.model;

public class User {
    private String firstName;
    private String lastName;
    private String gender;
    private String email;
    private String userName;
    private String password;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

接下來我們需要在控制器文件目錄下創建UserController控制器,然后呢,我們返回用戶視圖,如下方法:

    @RequestMapping(value = "/user", method = RequestMethod.GET)
    public ModelAndView user() {
        User user = new User();
        user.setGender("M");
        ModelAndView modelAndView = new ModelAndView("user", "command", user);
        return modelAndView;
    }

在前面內容我們通過字符串的形式返回的視圖,然后加上通過配置文件中視圖存放位置和加上后綴查找視圖,但是利用ModelAndView才是最友好的方式,通過其名稱添加模型和返回視圖應該就很清楚了,就像.NET MVC中的View方法一樣,我們可以返回模型數據,同時指定視圖名稱是一個道理。上述我們設置了性別的默認值為字符串M,我們暫且先說到這里,待會還要回過頭再次進行講解的,接下來我們去創建user.jsp,如下:

<%@ page language="java" contentType="text/html;" pageEncoding="utf-8" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<html>

<head>
    <title>Spring MVC Form</title>
    <link href="/static/css/bootstrap.min.css" rel="stylesheet">
    <link href="/static/css/bootstrap-theme.min.css" rel="stylesheet">
</head>

<body>
<div class="container">
    <div class="col-md-offset-2 col-md-7">
        <h2 class="text-center">Spring MVC 5 Form</h2>
        <div class="panel panel-info">
            <div class="panel-heading">
                <div class="panel-title">Sign Up</div>
            </div>
            <div class="panel-body">
                <form:form action="addUser" class="form-horizontal"
                           method="post" modelAttribute="user">

                    <div class="form-group">
                        <label for="firstName" class="col-md-3 control-label">First
                            Name</label>
                        <div class="col-md-9">
                            <form:input path="firstName" class="form-control"/>
                        </div>
                    </div>
                    <div class="form-group">
                        <label for="lastName" class="col-md-3 control-label">Last
                            Name</label>
                        <div class="col-md-9">
                            <form:input path="lastName" class="form-control"/>
                        </div>
                    </div>
                    <div class="form-group">
                        <form:label path="gender" class="col-md-3 control-label">gender</form:label>
                        <div class="col-md-9">
                            <form:radiobutton path="gender" value="M" label="Male"/>
                            <form:radiobutton path="gender" value="F" label="Female"/>
                        </div>
                    </div>
                    <div class="form-group">
                        <label for="userName" class="col-md-3 control-label">User
                            Name </label>
                        <div class="col-md-9">
                            <form:input path="userName" class="form-control"/>
                        </div>
                    </div>

                    <div class="form-group">
                        <label for="password" class="col-md-3 control-label">Password</label>
                        <div class="col-md-9">
                            <form:password path="password" class="form-control"/>
                        </div>
                    </div>
                    <div class="form-group">
                        <label for="email" class="col-md-3 control-label">Email</label>
                        <div class="col-md-9">
                            <form:input path="email" class="form-control"/>
                        </div>
                    </div>
                    <div class="form-group">
                        <div class="col-md-offset-3 col-md-9">
                            <form:button class="btn btn-primary">Submit</form:button>
                        </div>
                    </div>

                </form:form>
            </div>
        </div>
    </div>
</div>
<script type="text/javascript" src="/static/js/jquery.min.js"></script>
<script type="text/javascript" src="/static/js/bootstrap.min.js"></script>
</body>
</html>

首先我們必須在其頂部添加如下這一行表明我們要使用spring framework框架中的表單,且前綴為form:

<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>

在spring framework框架中對於表單中各個標簽的使用通過冒號隔開,如下:

<form:input path="firstName"/>
<form:input path="lastName"/>

最終在瀏覽器中將渲染成HTML標簽,還是非常簡單,這里我們只是稍微過一下,沒有太多復雜的東西,我們只要知道規則即可

<input name="firstName" type="text" value=""/>
<input name="lastName" type="text" value=""/>

我們在后台方法中返回模型即user,最終利用spring框架將模型上的屬性綁定到表單標簽上,那么接下來我們提交表單后,我們在后台怎么獲取到模型數據呢,其實我們在spring框架的表單標簽上定義了屬性modelAttribute,如下:

接下來我們在后台接收表單中的數據,如下:

    @RequestMapping(value = "/addUser", method = RequestMethod.POST)
    public String addUser(@ModelAttribute("user") User user,
                          ModelMap model) {
        model.addAttribute("firstName", user.getFirstName());
        model.addAttribute("lastName", user.getLastName());
        model.addAttribute("email", user.getEmail());
        model.addAttribute("userName", user.getUserName());
        model.addAttribute("password", user.getPassword());
        return "users";
    }

在后台我們同樣通過注解@ModelAttribute("user")與表單上定義的屬性modelAttribute="user"匹配,從而獲取數據,最終將獲取到的數據添加到ModelMap中,並返回視圖users.jsp中,如下:

<%@ page language="java" contentType="text/html;" pageEncoding="utf-8" %>
<%@taglib uri = "http://www.springframework.org/tags/form" prefix = "form"%>
<html>

<head>
    <title>Spring MVC Form Handling</title>
</head>

<body>
<h2>Submitted User Information</h2>
<table>
    <tr>
        <td>firstName</td>
        <td>${firstName}</td>
    </tr>
    <tr>
        <td>lastName</td>
        <td>${lastName}</td>
    </tr>
    <tr>
        <td>Email</td>
        <td>${email}</td>
    </tr>
    <tr>
        <td>userName</td>
        <td>${userName}</td>
    </tr>
    <tr>
        <td>Password</td>
        <td>${password}</td>
    </tr>
    <tr>
    </tr>
</table>
</body>
</html>

當我們啟動程序時發現報錯了,根據我們一路的解釋,似乎沒有任何毛病,這是何緣故, 上述異常大概是表明特性user出了問題,其實原因出在后台獲取用戶視圖上,如下這一行上:

ModelAndView modelAndView = new ModelAndView("user", "command", user);

如上構造函數中的第一個參數代表要渲染的視圖名稱,而第三個參數是在視圖中要綁定的模型,那么第二個參數是個什么鬼呢?一般情況下這個值都會默認設置成command,據查資料這個字符串在spring框架是一個常量,那么它的作用是什么呢?我個人猜測如果默認設置成該值,那么就代表注解@ModelAttribute的參數值就是實體類名稱,比如我們實體類為User,那么默認ModelAttribute參數名就是user,我們無需在上述表單上指定modelAttribute的值,即使指定為user也會拋出上述異常。如果在表單上顯式定義了modelAttribute的值,那么在實例化模型視圖類時,第二個參數必須與其值相等,我們將上述command修改為user即可解決問題,不信的話,你可以試試。同時在接收表單提交的參數時,經過測試,后台的注解@ModelAttribute可去除參數名稱也可綁定。最終根據我們填寫表單的內容和渲染結果,如下:

 

上述我們是通過ModelAndView返回模型和視圖,這只是實現方式之一,其他實現將又會引來問題,接下來我們直接返回該模型,此時將以該模型名稱作為作為表單上屬性modelAttribute的名稱,此時必須顯式設置modelAttribute="user",如下:

    @RequestMapping(value = "/user", method = RequestMethod.GET)
    public User user() {
        User user = new User();
        user.setGender("M");
        user.setFavorites(new String[]{"乒乓球", "羽毛球", "台球"});
        return user;
    }

如若在上述表單上沒有顯式設置modelAttribute="user"且值不能為其他值,否則將拋出如下異常:

 

若我們想將上述表單上的屬性modelAttribute的值user,設置為其他值,比如modelAttribute="User",此時必須在該視圖對應方法上通過注解@ModelAttribute顯式設置名稱為User,否則同樣將拋出上述異常,如下:

    @ModelAttribute("User")
    @RequestMapping(value = "/user", method = RequestMethod.GET)
    public User user() {
        User user = new User();
        user.setGender("M");
        user.setFavorites(new String[]{"乒乓球", "羽毛球", "台球"});
        return user;
    }

到此為止我們學習到了ModelAndView用來設置模型和視圖名稱,而注解@ModelAttribute則是控制器和視圖綁定數據的橋梁,該注解既可作為方法參數接收視圖模型數據,也可修飾控制器方法將模型數據綁定到視圖。ModelMap則是映射模型數據,當然也支持集合和合並特性等等。我們再來演示通過對方法進行注解@ModelAttribute,然后綁定到視圖中,在User類中我們再定義一個屬性country,如下:

    private String country;
 
    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

在控制器中通過注解@ModelAttribute定義國家元數據,並綁定到視圖上,如下:

    @ModelAttribute("countryList")
    public Map<String, String> getCountryList() {
        Map<String, String> countryList = new HashMap<>();
        countryList.put("CHI", "中國");
        countryList.put("CH", "英國");
        countryList.put("SG", "新加坡");
        return countryList;
    }
<div class="form-group">
   <label for="country" class="col-md-3 control-label">Country</label>
        <div class="col-md-9">
            <form:select path = "country">
                 <form:option value = "無" label = "請選擇"/>
                 <form:options items = "${countryList}" />
            </form:select>
         </div>
 </div>

在視圖中我們通過$符號渲染數據,當然我么也可以在視圖中寫Java代碼,這和.NET或.NET Core中的Razor視圖一樣,只不過在JSP中通過<%  代碼 %>來寫代碼,我們同樣也來演示下,在User中再定義一個數組,如下:

    private String[] favorites;

    public  String[] getFavorites(){
        return favorites;
    }

    public void setFavorites(String[] favorites) {
        this.favorites = favorites;
    }

在獲取user.jsp視圖對應后台方法中,我們設置上述定義的愛好列表默認值,如下:

user.setFavorites(new String[]{"乒乓球", "羽毛球", "台球"});

同時在控制器中我們定義愛好列表,然后綁定到視圖中,對默認設置的愛好列表通過checkbox進行選中

    @ModelAttribute("favorites")
    public Object[] getfavoriteList() {
        List<String> favorites = new ArrayList<>();
        favorites.add("足球");
        favorites.add("乒乓球");
        favorites.add("羽毛球");
        favorites.add("台球");
        return favorites.toArray();
    }

再在user.jsp視圖中,添加對愛好列表數據的綁定和選中,如下:

<div class="form-group">
    <label for="favorites" class="col-md-3 control-label">Favorites</label>
       <div class="col-md-9">
          <form:checkboxes class="f" items = "${favorites}" path = "favorites" />
       </div>
 </div>

然后在獲取提交表單的方法中,獲取上述我們添加的城市和選中的愛好列表,如下:

最后在渲染提交表單的視圖users.jsp中,當獲取愛好列表時,此時通過代碼的形式進行渲染,如下:

 <tr>
        <td>Country</td>
        <td>${country}</td>
    </tr>
    <tr>
        <td>
            <% String[] favorites = (String[])request.getAttribute("favorites");
                for(String favorite: favorites) {
                    out.println(favorite);
                }
            %>
        </td>
    </tr>

 

總結 

本節我們詳細分析了在Spring MVC中對於參數的綁定,最需要注意的是在模型綁定到視圖中的問題,這里我們下個結論: 若通過ModelAndView設置模型和視圖時,若此時第二個參數為command,則表單上的屬性modelAttribute默認值為模型名稱,此時若顯式設置值為模型名稱將拋出異常,若顯式設置該屬性的值不為模型名稱,此時需要將command設置與其一樣,否則將拋出異常。若直接返回模型時,此時表單上的屬性modelAttribute的值必須顯式設置為模型名稱,否則將拋出異常,若表單上的屬性modelAttribute值不為模型名稱時,此時必須在該視圖對應方法上顯式設置注解@ModelAttribute其名稱與其一致,否則將拋出異常。其他的地方我們只需要了解對應使用規則沒有什么難點,好了,本節的內容我們到此為止,下一節我們陸續開始於數據庫打交道,感謝您的閱讀,我們下節見。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM