Spring MVC数据绑定和响应 你了解多少?
数据绑定的概念
在程序运行时,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/>密 码:<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; //商品idprivate String proName; //商品名称//get set方法自己实现
}
创建一个提交商品页面products.jsp(直接创建文件并复制内容到webapp下)
<%--Created by IntelliJ IDEA.User: lenovoDate: 2025/3/9Time: 16:23To 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中的具体对象的具体属性的名称保持一致。