Spring MVC数据绑定和响应 你了解多少?

发布于:2025-05-14 ⋅ 阅读:(11) ⋅ 点赞:(0)

数据绑定的概念

在程序运行时,Spring MVC接收到客户端的请求后,会根据客户端请求的参数和请求头等数据信息,将参数以特定的方式转换并绑定到处理器的形参中。Spring MVC中将请求消息数据与处理器的形参建立连接的过程就是Spring MVC的数据绑定。

Spring MVC数据绑定的过程图

SpringMVC数据绑定中的信息处理过程的步骤描述如下:

(1)Spring MVC将ServletRequest对象传递给DataBinder。

(2)将处理方法的入参对象传递给DataBinder。

(3)DataBinder调用ConversionService组件进行数据类型转换、数据格式化等工作,并将ServletRequest对象中的消息填充到参数对象中。

(4)调用Validator组件对已经绑定了请求消息数据的参数对象进行数据合法性校验。

(5)校验完成后会生成数据绑定结果BindingResult对象,Spring MVC会将BindingResult对象中的内容赋给处理方法的相应参数。

简单数据绑定

1 默认类型数据绑定

当使用Spring MVC默认支持的数据类型作为处理器的形参类型时,Spring MVC的参数处理适配器会默认识别这些类型并进行赋值。Spring MVC常见的默认类型如下所示。

• HttpServletRequest:通过request对象获取请求信息。

• HttpServletResponse:通过response处理响应信息。

• HttpSession:通过session对象得到session中存放的对象。

• Model/ModelMap:Model是一个接口,ModelMap是一个类,Model的实现类对象和ModelMap对象都可以设置model数据,model数据会填充到request域。

@Controller
public class UserController {
    @RequestMapping("/getUserId")
    public void getUserId(HttpServletRequest request){
        String userid= request.getParameter("userid");
        System.out.println("userid="+userid);
    }
}

通过http://localhost:8080/FirstMVCDemo/getUserId?userid=1 访问,结果如下:

2 简单数据类型绑定

简单数据类型的绑定,就是指Java中基本类型(如int、double、String等)的数据绑定。在Spring MVC中进行简单类型的数据绑定,只需客户端请求参数的名称和处理器的形参名称一致即可,请求参数会自动映射并匹配到处理器的形参完成数据绑定。

@RequestMapping("/getUserNameAndId")
public void getUserNameAndId(String username, Integer id) {
    System.out.println("username=" + username + ",id=" + id);
}

通过 http://localhost:8080/FirstMVCDemo/getUserNameAndId?username=Spring&id=1 访问:

参数别名的设置

需要注意的是,有时候客户端请求中参数名称和处理器的形参名称不一致,这就会导致处理器无法正确绑定并接收到客户端请求中的参数。为此,Spring MVC提供了@RequestParam注解来定义参数的别名,完成请求参数名称和处理器的形参名称不一致时的数据绑定。

@RequestParam注解

属性 说明
value name属性的别名,这里指参数的名称,即入参的请求参数名称,如value="name"表示请求的参数中,名称为name的参数的值将传入。如果当前@RequestParam注解只使用vaule属性,则可以省略value属性名,如@RequestParam("name")
name 指定请求头绑定的名称
required 用于指定参数是否必须,默认是true,表示请求中一定要有相应的参数
defaultValue 形参的默认值,表示如果请求中没有同名参数时的默认值
@RequestMapping("/getUserName")
public void getUserName(@RequestParam(value="name",required = false,defaultValue = "lq") String username) {
       System.out.println("username = "+username);
}

访问 http://localhost:8080/FirstMVCDemo/getUserName,结果为默认username值lq

访问 http://localhost:8080/FirstMVCDemo/getUserName?name=xiaoyan,结果如下:

@RequestParam注解的value属性,给getUserName()方法中的username形参定义了别名name。此时,客户端请求中名称为name的参数,就会绑定到getUserName()方法中的username形参上。@RequestParam注解的required属性设定了请求的name参数不是必须的,如果访问时没有携带name参数,会将defaultValue属性设定的值赋给形参username。

@PathVariable注解

当请求的映射方式是REST风格时,上述对简单类型数据绑定的方式就不适用了。为此,Spring MVC提供了@PathVariable注解,通过@PathVariable注解可以将URL中占位符参数绑定到处理器的形参中。@PathVariable注解有以下两个常用属性。

• value:用于指定URL中占位符名称。

• required:是否必须提供占位符,默认值为true。

//通过@PathVariable注解的value属性将占位符参数“name”和处理方法的参数username进行绑定
@RequestMapping("/user/{name}")
public void getPathVariable(@PathVariable(value = "name") String username){
    System.out.println("username="+username);
}

访问方式:http://localhost:8080/FirstMVCDemo/user/xiaoyan

从运行结果的打印信息可以看出,控制台打印出了username的值为xiaoyan。这表明访问地址后执行了getPathVariable()方法,@PathVariable注解成功将请求URL中的变量user映射到了方法的形参username上。如果请求路径中占位符的参数名称和方法形参名称一致,那么@PathVariable注解的value属性可以省略。

 @RequestMapping("/user/{name}")
    public void getPathVariable(@PathVariable String name){
        System.out.println("username="+name);
    }

3 POJO绑定

POJO数据绑定的使用场景

在使用简单数据类型绑定时,可以很容易的根据具体需求来定义方法中的形参类型和个数,然而在实际应用中,客户端请求可能会传递多个不同类型的参数数据,如果还使用简单数据类型进行绑定,那么就需要手动编写多个不同类型的参数,这种操作显然比较繁琐。为解决这个问题,可以使用POJO类型进行数据绑定。

POJO类型的数据绑定就是将所有关联的请求参数封装在一个POJO中,然后在方法中直接使用该POJO作为形参来完成数据绑定。

 // 接收表单用户信息
@RequestMapping("/registerUser")
public void registerUser(User user) {
    String username = user.getUsername();
    String password = user.getPassword();
    System.out.println("username="+username+",password="+password);
}

register.jsp如下(注意register.jsp创建的地方在webapp下,而不是WEB-INF下)

<%--防止页码中文乱码--%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>注册</title></head>
<body>
<form action="${pageContext.request.contextPath}/registerUser" method="post">
    用户名:<input type="text" name="username"/><br/>
    密&nbsp;&nbsp;&nbsp;码:<input type="password" name="password"/><br/>
    <input type="submit" value="注册"/>
</form>
</body>
</html>

文件目录如下:

访问URL:http://localhost:8080/FirstMVCDemo/register.jsp

页面展示如下:

在register.jsp所示页面的表单中,分别填写注册的用户名为你的名字“张三”,密码为“123”,然后单击“注册”按钮即可完成注册数据的提交。当单击“注册”按钮后,控制台打印信息如图所示。

从图中可以看出,程序成功打印出了用户名和密码。这表明registerUser()方法获取到了客户端请求中的参数username和参数password的值,并将username和password的值分别赋给了getUserNameAndId( )方法中user形参的username属性和password属性,实现了POJO数据绑定。

解决请求参数中的中文乱码问题

为了防止客户端传入的中文数据出现乱码,可以使用Spring提供的编码过滤器来统一编码。要使用编码过滤器,只需要在web.xml中添加如下代码。

<filter>
       <filter-name>encodingFilter</filter-name>
       <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
       <init-param>
           <param-name>encoding</param-name>
           <param-value>UTF-8</param-value>
       </init-param>
       <init-param>
           <param-name>forceEncoding</param-name>
           <param-value>true</param-value>
       </init-param>
   </filter>
   <filter-mapping>
       <filter-name>encodingFilter</filter-name>
       <url-pattern>/*</url-pattern>
   </filter-mapping>

上述代码中,在<filter>元素中,首先使用<fillter-class>元素配置了编码过滤器类org.springframework.web.filter.CharacterEncodingFilter,然后使用<init-param>元素设置统一的编码为UTF-8。最后配置<filter-mapping>元素,拦截前端页面中的所有请求,并交由名称为CharacterEncodingFilter的编码过滤器类进行处理,将所有的请求信息内容以UTF-8的编码格式进行解析。

注意:以上可以解决post请求乱码问题,对于get请求中文参数出现乱码,可以在使用参数之前重新编码,如String username = new String(user.getUsername().getBytes(“ISO8859-1”),“UTF-8”);,其中ISO8859-1是Tomcat默认编码,需要将Tomcat编码后的内容再按UTF-8编码。

复杂数据绑定

在实际开发中,可能会遇到客户端请求需要传递多个同名参数到服务器端的情况,这种情况采用前面讲解的简单数据绑定的方式显然是不合适的。此时,可以使用数组来接收客户端的请求参数,完成数据绑定。

数组绑定

创建一个商品类Product:

package com.lq.pojo;

/**
 * @Author: Luqing Teacher
 * @CreateTime: 2025-03-09
 * @Description:
 * @Version: 1.0
 */


public class Product {
    private String proId;		//商品id
    private String proName;	//商品名称

   //get set方法自己实现
}

创建一个提交商品页面products.jsp(直接创建文件并复制内容到webapp下)

<%--
  Created by IntelliJ IDEA.
  User: lenovo
  Date: 2025/3/9
  Time: 16:23
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8" %>
<html>
<head><title>提交商品</title></head>
<body>
<form action="${pageContext.request.contextPath }/getProducts" method="post">
    <table width="220px" border="1">
        <tr>
            <td>选择</td>
            <td>商品名称</td>
        </tr>
        <tr>
            <td>
                <input name="proIds" value="1" type="checkbox">
            </td>
            <td>Spring框架实战</td>
        </tr>
        <tr>
            <td>
                <input name="proIds" value="2" type="checkbox">
            </td>
            <td>SpringMVC框架实战</td>
        </tr>
        <tr>
            <td>
                <input name="proIds" value="3" type="checkbox">
            </td>
            <td>SSM框架实战</td>
        </tr>
    </table>
    <input type="submit" value="提交商品"/>
</form>
</body>
</html>

创建一个商品处理器类ProductController

@Controller
public class ProductController {
    // 获取商品列表
    @RequestMapping("/getProducts")
    public void getProducts(String[] proIds) {
        for (String proId : proIds) {
            System.out.println("获取到了Id为"+proId+"的商品");	}
    }
}

访问URL:http://localhost:8080/FirstMVCDemo/products.jsp

勾选products.jsp显示效果图中所示的全部复选框,然后单击“提交商品”按钮,控制台打印信息如下图所示:

集合绑定

集合中存储简单类型数据时,数据的绑定规则和数组的绑定规则相似,需要请求参数名称与处理器的形参名称保持一致。不同的是,使用集合绑定时,处理器的形参名称需要使用@RequestParam注解标注。

接下来使用集合数据绑定来批量提交商品案例,具体实现步骤如下所示。在ProductController.java类中创建getProductList(​方法,让getProductList(​方法使用List类型来接受客户端的请求参数,具体代码如下所示:

// 获取商品列表(使用List绑定数据)
@RequestMapping("/getProductList")
public void getProductList(@RequestParam("proIds") List<String> proIds) {
    for (String proId : proIds) {
        System.out.println("获取到了Id为" + proId + "的商品");
    }
}

访问:http://localhost:8080/FirstMVCDemo/products.jsp

注意:@RequestParam注解解决集合绑定的异常问题

如果getProductList( )方法中不使用@RequestParam注解,Spring MVC默认将List作为对象处理,赋值前先创建List对象,然后将proIds作为List对象的属性进行处理。由于List是接口,无法创建对象,所以会出现无法找到构造方法异常。如果将类型更改为可创建对象的类型,如ArrayList,可以创建ArrayList对象,但ArrayList对象依旧没有proIds属性,因此无法正常绑定,数据为空。此时需要告知Spring MVC的处理器proIds是一组数据, 而不是一个单一数据。通过@RequestParam注解,将参数打包成参数数组或集合后,Spring MVC才能识别该数据格式,并判定形参类型是否为数组或集合,并按数组或集合对象的形式操作数据。

复杂POJO绑定

使用简单POJO类型已经可以完成多数的数据绑定,但有时客户端请求中传递的参数比较复杂。例如,在用户查询订单时,页面传递的参数可能包括订单编号、用户名称等信息,这就包含了订单和用户两个对象的信息。如果将订单和用户的所有查询条件都封装在一个简单POJO中,显然会比较混乱,这时可以考虑使用复杂POJO类型的数据绑定。

所谓的复杂POJO,就是POJO属性的类型不止包含简单数据类型,还包含对象类型、List类型和Map类型等其他引用类型。接下来分别对复杂POJO中属性为对象类型的数据绑定、属性为List类型的数据绑定和属性为Map类型的数据绑定进行讲解

属性为对象类型的数据绑定

创建一个订单类Order,用于封装订单信息

public class Order {
    private String orderId;

    public String getOrderId() {
        return orderId;
    }

    public void setOrderId(String orderId) {
        this.orderId = orderId;
    }
}

修改User.java类,在User类中新增Order类型的属性order,并定义相应的getter和setter方法。修改后User类的具体代码如下所示。

public class User {

    private Integer id;
    private String username;

    private String password;

    private Order order;		//订单

    //get set方法自己实现
}

在UserController.java类中定义方法findOrderWithUser( ),用于获取客户端请求中的User信息,findOrderWithUser( )方法的具体代码如下所示。

 @RequestMapping("/findOrderWithUser")
public void findOrderWithUser(User user) {
    String username = user.getUsername();
    String orderId = user.getOrder().getOrderId();
    System.out.println("username="+username+",orderId="+orderId);
}

在项目的src\main\webapp目录下,创建一个订单信息文件order.jsp,在order.jsp文件中创建一个表单,表单中包含用户名和订单编号。表单提交时将用户名和订单编号信息发送到处理器。order.jsp的具体代码如下所示:

<%@ page language="java" contentType="text/html;
	charset=UTF-8" pageEncoding="UTF-8" %>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>订单信息</title></head>
<body>
<form action="${pageContext.request.contextPath}/findOrderWithUser" method="post">
    所属用户:<input type="text" name="username"/><br/>
    订单编号:<input type="text" name="order.orderId"/><br/>
    <input type="submit" value="查询"/>
</form>
</body>
</html>

在复杂POJO数据绑定时,如果数据需要绑定到POJO属性对象的属性中,客户端请求的参数名(本例中指form表单内各元素name的属性值)的格式必须为“属性对象名称​.​属性​”,其中“属性对象名称”要和POJO的属性对象名称一致,“属性”要和属性对象所属类的属性一致。

分别在输入框中输入:哪吒手办 1234

属性为List类型的数据绑定

一般订单业务中,用户和订单基本都是一对多的映射关系,即用户的订单属性使用集合类型。接下来通过一个获取用户订单信息的例子,演示复杂POJO中属性为List类型的数据绑定,案例具体实现步骤如下。修改User.java类,将User类中订单属性修改为List类型。由于用户一般拥有多个收货地址,在User类中新增List类型的地址属性。

    private List<Order> orders;		//用户订单
    private List<String> address;		//订单地址

创建一个订单处理器类OrderController,在OrderController类中定义showOrders( ) 方法,用于展示用户的订单信息。


@Controller
public class OrderController {//  获取用户中的订单信息

    @RequestMapping("/showOrders")
    public void showOrders(User user) {
        List<Order> orders = user.getOrders();
        List<String> addressList = user.getAddress();
        System.out.println("订单:");
        for (int i = 0; i < orders.size(); i++) {
            Order order = orders.get(i);
            String address = addressList.get(i);
            System.out.println("订单Id:" + order.getOrderId());
            System.out.println("订单配送地址:" + address);
        }
    }
}

在项目的src\main\webapp目录下,创建一个订单信息文件orders.jsp,在orders.jsp中创建一个表单用于提交用户的订单信息。表单提交时,表单数据分别封装到User的订单属性orders和地址属性address中。orders.jsp的具体代码如下所示

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<html>
<head><title>订单信息</title></head>
<body>
<form action="${pageContext.request.contextPath }/showOrders" method="post">
    <table width="220px" border="1"><!-- 下面只展示一条数据-->
        <tr>
            <td>订单号</td>
            <td>订单名称</td>
            <td>配送地址</td>
        </tr>
        <tr>
            <td><input name="orders[0].orderId" value="1" type="text"></td>
            <td><input name="orders[0].orderName" value="Java基础教程" type="text"></td>
            <td><input name="address" value="北京海淀" type="text"></td>
        </tr>
    </table>
    <input type="submit" value="订单信息"/>
</form>
</body>
</html>

查看结果:

属性为Map类型的数据绑定

接下来,通过一个获取订单信息的案例,演示复杂POJO中属性为Map类型的数据绑定,具体实现如下。修改Order.java类,在Order类中新增HashMap类型的属性productInfo,用于封装订单中的商品信息,其中productInfo的键用来存放商品的类别,productInfo的值用来存放商品类别对应的商品。

private HashMap<String,Product> productInfo;	//商品信息

public HashMap<String, Product> getProductInfo() {
    return productInfo;
}

public void setProductInfo(HashMap<String, Product> productInfo) {
    this.productInfo = productInfo;
}

修改OrderController.java类,在OrderController类中新增getOrderInfo()方法,用于获取客户端提交的订单信息,并将获取到的订单信息打印在控制台。getOrderInfo()方法的具体代码如下所示:

  @RequestMapping("/orderInfo")
    public void getOrderInfo(Order order) {
        String orderId = order.getOrderId();        //获取订单id
        //获取商品信息
        HashMap<String, Product> orderInfo = order.getProductInfo();
        Set<String> keys = orderInfo.keySet();
        System.out.println("订单id:" + orderId);
        System.out.println("订单商品信息:");
        for (String key : keys) {
            Product product = orderInfo.get(key);
            String proId = product.getProId();
            String proName = product.getProName();
            System.out.println(key + "类~" + "商品id:" + proId + ",商品名称:" + proName);
        }
    }

在项目的src\main\webapp目录下,创建一个订单信息页面order_info.jsp,在order_info.jsp中创建一个表单用于提交订单信息。表单提交时,表单数据分别封装到Order的orderId属性和商品信息属性productInfo中。

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!-- 只展示了form表单的内容,和一条标签内容-->
<form action="${pageContext.request.contextPath}/orderInfo" method="post">
    <table border="1">
        <tr>
            <td colspan="2">
                订单id:<input type="text" name="orderId" value="1"></td>
        </tr>
        <tr>
            <td>商品Id</td>
            <td>商品名称</td>
        </tr>
        <tr>
            <td><input name="productInfo['生鲜'].proId" value="1" type="text"></td>
            <td><input name="productInfo['生鲜'].proName" value="三文鱼" type="text"></td>
        </tr>
        <tr></tr>
    </table>
    <input type="submit" value="提交"/>
</form>

页面如下,点击提交:

结果如下:

注意:数据绑定到Map类型的属性时的参数命名要求

在复杂POJO数据绑定时,如果数据绑定到Map类型的属性,客户端请求的参数名称(本例中指form表单内各元素name的属性值)必须与POJO类的层次结构名称保持一致,并使用键值的映射格式描述对象在Map中的位置,即客户端参数名称必须和要绑定的Map中的具体对象的具体属性的名称保持一致。