数据绑定概念及绑定过程
在springmvc入门介绍(一)中介绍了几种服务器端的数据如何传递给前端去展示。本节主要介绍如何将前端的数据传送到服务端,即view的数据如何传送到controller中对应的处理方法中。springmvc主要将要传送的数据通过与处理方法的形参进行绑定,完成数据传递的过程。绑定过程如下图所示:
SpringMVC框架提供了一个数据绑定组件(DataBinder),前端请求将请求数据包裹在request中发送给DataBinder组件,通过RequestMaping映射的方法也将自己的入参交给绑定组件,DataBinder调用ConversionService组件进行数据类型转换、数据格式化等工作,并将ServletRequest对象中的消息填充到参数对象中;再调用Validator组件对已经绑定了请求消息数据的参数对象进行数据合法性校验;校验完成后生成数据绑定结果BindingResult对象,springmvc将此对象中内容赋给处理方法相应的参数。
根据客户端请求参数类型和个数不同,我们将数据绑定分为默认参数类型、简单参数类型和复杂参数类型,以下前端使用jsp、处理方法的返回值为string为例,详细介绍数据绑定过程。示例项目工程目录如下:
为了简单期间,不访问数据库使用静态数据作为示例,controller准备工作代码如下:
@Controller
@RequestMapping("/product")
public class product {
private static List<ProductVO> products = new ArrayList<>();
static {
ProductVO product = new ProductVO();
product.setId(1);
product.setProduceName("联想笔记本");
product.setProduceDesc("thinkpad T 系列");
products.add(product);
ProductVO product2 = new ProductVO();
product2.setId(2);
product2.setProduceName("Dell笔记本");
product2.setProduceDesc("移动工作站 系列");
products.add(product2);
ProductVO product3 = new ProductVO();
product3.setId(3);
product3.setProduceName("HP笔记本");
product3.setProduceDesc("ProBook 系列");
products.add(product3);
}
@RequestMapping("/list")
public String getList(Model model){
model.addAttribute("datas",products);
return "WEB-INF/view/ListPage";
}
}
在浏览器URL栏输入:http://localhost:8080/webapp/product/list,展示列表页面,我们以商品的增加、删除、修改为例介绍springmvc数据绑定
默认参数类型
后台方法的形参中直接使用Spring MVC提供的默认参数类型进行数据绑定,常用的默认参数类型包括:
- HttpServletRequest:通过request对象获取请求信息;
- HttpServletResponse:通过response处理响应信息;
- HttpSession:通过session对象得到session中存放的对象;
- Model/ModelMap:Model是一个接口,ModelMap是一个接口实现,作用是将model数据填充到request域
查询商品名称为"联想笔记本"的数据,使用HttpServletRequest携带参数:
jsp查询部分代码如下:
<form action="${pageContext.request.contextPath}/product/query1" method="post">
查询条件
<table>
<tr>
<td>
<label>商品名称: </label><input type="text" name="productName" value=""/>
<input type="submit" value="查询"/>
</td>
</tr>
</table>
</form>
controller中方法如下,使用HttpServletRequest传入参数,使用HttpServletResponse返回。
//默认支持类型,HttpServletRequest、HttpServletResponse、HttpSession、Model等
@RequestMapping(value = "/query1", method={RequestMethod.POST,RequestMethod.GET})
public void queryProduct1(HttpServletRequest request, HttpServletResponse response) throws IOException {
String productName = request.getParameter("productName");
System.out.println(productName);
Iterator<ProductVO> it = products.iterator();
while(it.hasNext()){
if(!productName.equals(it.next().getProduceName())){
it.remove();
}
}
response.sendRedirect(request.getContextPath()+"/product/list");
return ;
}
通过前端form表单绑定request域值传递参数。
简单参数
简单参数包括基本数据类型如:int、boolean、double等,包装类型如:Integer、Boolean、Double、String、pojo类型等。以下分别通过查询传送商品名称(String)和商品的添加为例进行验证。
验证String接收参数,jsp代码如上不变,controller代码片段如下:
//java基本类型和包装类型绑定,int、double、Integer、Double、String等
@RequestMapping(value = "/query2", method={RequestMethod.POST,RequestMethod.GET})
public String queryProduct2(Model model, String productName){
System.out.println(productName);
Iterator<ProductVO> it = products.iterator();
while(it.hasNext()){
if(!productName.equals(it.next().getProduceName())){
it.remove();
}
}
model.addAttribute("datas",products);
return "WEB-INF/view/ListPage";
}
这里String携带参数productName,使用String返回逻辑视图名,Model携带参数返回。注意:这里形参名必须和form表单中绑定的input name完全一致,否则报错。
针对上述问题,SpringMVC提供了一个注解符@RequestParam解决此问题。代码如下:
//基本参数类型绑定,当名称不一致时操作
@RequestMapping(value = "/query3", method={RequestMethod.POST,RequestMethod.GET})
public String queryProduct3(Model model, @RequestParam("productName") String name){
System.out.println(name);
Iterator<ProductVO> it = products.iterator();
while(it.hasNext()){
if(!it.next().getProduceName().contains(name)){
it.remove();
}
}
model.addAttribute("datas",products);
return "WEB-INF/view/ListPage";
}
通过注解符@RequestParam完成前端绑定的名称和后台处理方法使用的参数变量解耦。假设查询条件很多,那么一个条件一个形参,这样是能够解决问题,但是不是很方便,那么我们可以pojo类型解决问题,在这里我们使用ProductVO进行验证。jsp代码片段如下:
<form action="${pageContext.request.contextPath}/product/query4" method="post">
查询条件
<table>
<tr>
<td>
<label>商品ID: </label><input type="text" name="id" value=""/>
<label>商品名称: </label><input type="text" name="produceName" value=""/>
<label>商品描述: </label><input type="text" name="produceDesc" value=""/>
<input type="submit" value="查询"/>
</td>
</tr>
</table>
</form>
接收代码如下,这里仅为验证,将接收过来的数据输出到控制台上
//基本参数类型绑定,当名称不一致时操作
@RequestMapping(value = "/query4", method={RequestMethod.POST,RequestMethod.GET})
public String queryProduct4(ProductVO productVO){
System.out.println(productVO.toString());
return "WEB-INF/view/Success";
}
运行系统,输入参数名称:
查看控制台输出,结果如下:
表明已经使用ProductVO接收到了对象。再考虑一种复杂情况,如:商品是一个pojo对象,生产厂家也是一个pojo对象,商品类中持有了对象的引用,此时如何处理,pojo代码如下
public class FactoryVO {
private Integer id;
private String FactoryName;
private String FactoryAdress;
//省略get、set
@Override
public String toString() {
return "FactoryVO{" +
"id=" + id +
", FactoryName='" + FactoryName + '\'' +
", FactoryAdress='" + FactoryAdress + '\'' +
'}';
}
}
public class ProductVO2 {
private int id;
private String produceName;
private String ProduceDesc;
private FactoryVO factoryVO;
//省略get、set
@Override
public String toString() {
return "ProductVO{" +
"id=" + id +
", produceName='" + produceName + '\'' +
", ProduceDesc='" + ProduceDesc + '\'' +
", 厂家信息="+ factoryVO.toString()+'\'' +
'}';
}
}
控制台输出结果:
使用pojo完成传参时,需要表单上绑定name与pojo的属性名一致,如果不匹配的属性制null。
自定义绑定
springmvc提供的数据类型转换基本能满足我们的开发,但也有特殊数据类型无法直接进行数据绑定,必须先经过数据转换,例如日期数据,pringmvc也允许我们开发一些自定义的类型转换器,完成特殊类型的转换,spring提供了两个接口(Converter和Formatter)在我们开发自定义类型转换器时使用,如:我们开发一个日期类型转换器,就需要完成以下代码。
public class DataConvert implements Converter<String, Date> {
@Override
public Date convert(String s) {
System.out.println("调用自定义日期转换器");
if(s!=null && !"".equals(s)){
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
try {
return dateFormat.parse(s);
} catch (ParseException e) {
e.printStackTrace();
}
}
return null;
}
}
配置写好的类型转换器,打开springmvc.xml文件,配置代码如下:
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
<!--将自定义的转换器交给spring进行管理-->
<bean id="dataConvert" class="com.bjwl.common.DataConvert"></bean>
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<!--注入自定义转换器-->
<property name="converters" ref="dataConvert" />
</bean>
验证代码如下:
@RequestMapping("/edit1")
public String edit1(Date createTime){
System.out.println(createTime);
return "WEB-INF/view/Success";
}
打开浏览器,在地址栏输入:http://localhost:8080/webapp/product/edit1?createTime=2019-01-01 10:10。查看后台控制台输出如下:
将前端的String转换为参数为日期的值。注意:前端输入字符串格式应该与转换器中的格式完全一致,上例中,字符串格式为"yyyy-MM-dd HH:mm";带时分的,如果输入不带时分的字符串,如:2019-01-01,则会报错
复杂参数绑定
以上介绍了简单参数类型的绑定,如果要绑定数组、集合这些实际开发中也是十分常见的复杂参数又该如何做呢?以下就简单演示一下数组绑定和集合绑定。
数组绑定:假如要根据商品的id批量删除商品,就需要使用到数组。修改jsp页面,页面代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="C" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>
<head>
<title>列表页面</title>
</head>
<body>
<form name="frm" action="${pageContext.request.contextPath}/product/deleteIds" method="post">
<input type="submit" value="批量删除" >
<table border="1">
<tr>
<td>序号</td>
<td>商品名称</td>
<td>商品描述</td>
<td>删除</td>
</tr>
<C:forEach items="${datas}" var="item">
<tr>
<td><span>${item.id}</span></td>
<td><span>${item.produceName}</span></td>
<td><span>${item.produceDesc}</span></td>
<td><a href="${pageContext.request.contextPath}/product/modfy?id=${item.id}">修改</a></td>
<td><input type="checkbox" name="ids" value="${item.id}"/></td>
</tr>
</C:forEach>
</table>
</form>
</body>
</html>
注意:这里checkbox name为ids。后端接收的controller方法为:
@RequestMapping("/deleteIds")
public String deleteData(Integer[] ids){
for (int i = 0; i <ids.length ; i++) {
System.out.println(ids[i]);
}
return "WEB-INF/view/Success";
}
点击批量删除按钮,控制台显示结果如下:
集合绑定:批量修改产品时,需要一次提交多个pojo对象,就要使用到集合绑定。jsp代码修改为:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="C" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<html>
<head>
<title>列表页面</title>
</head>
<body>
<form name="frm" action="${pageContext.request.contextPath}/product/saveDatas" method="post">
<input type="submit" value="保存修改">
<table border="1">
<tr>
<td>序号</td>
<td>商品名称</td>
<td>商品描述</td>
<td>删除</td>
</tr>
<C:forEach items="${datas}" var="item" varStatus="s">
<tr>
<td><input type="input" name="products[${s.index}].id" value="${item.id}"/></td>
<td><input type="input" name="products[${s.index}].produceName" value="${item.produceName}"/></td>
<td><input type="input" name="products[${s.index}].produceDesc" value="${item.produceDesc}"/></td>
<td><a href="${pageContext.request.contextPath}/product/edit?id=${item.id}">修改</a></td>
<td><input type="checkbox" name="ids" value="${item.id}"/></td>
</tr>
</C:forEach>
</table>
</form>
</body>
</html>
集合绑定时,需要创建一个新的vo对象,用于存放List,代码如下:
public class ProductList {
public List<ProductVO> getProducts() {
return products;
}
public void setProducts(List<ProductVO> products) {
this.products = products;
}
private List<ProductVO> products;
}
使用这个对象来接收参数,controller方法代码为:
@RequestMapping("/saveDatas")
public String saveDatas(ProductList products){
for (ProductVO item :products.getProducts()){
System.out.println(item.toString());
}
return "WEB-INF/view/Success";
}
运行后,控制台显示以下信息:
这里需要注意的使用集合时,必须使用一个包装类,将集合包装类的内部,如:ProductList 就持有 List products集合对象,此时jsp页面要求使用包装类的属性products去接收参数,页面绑定名称与属性名不匹配,返回null。
小结
参数绑定是将前端数据提交给后端控制器重要部分,虽然前后端分离项目中使用异步通讯,来回传递的是json,页面数据通过双向绑定来实现,但这些是基础,务必掌握使用方式。