当用户在页面触发某种请求时,一般会将一些参数(key/value)带到后台。在Spring MVC中可以通过参数绑定,将客户端请求的key/value数据绑定到Controller处理器方法的形参上,显然,这是很关键的一个问题。
当用户发送一个请求时,根据Spring MVC的请求处理流程,前端控制器会请求处理器映射器HandlerMapping返回一个处理器(或处理器链),然后请求处理器适配器HandlerAdapter执行相应的Handler处理器。此时,处理器适配器HandlerAdapter会调用Spring MVC提供的参数绑定组件将请求的key/value数据绑定到Controller处理器方法对应的形参上。
关于Spring MVC的参数绑定组件,早期版本中使用PropertyEditor,其只能将字符串转换为Java对象,而后期版本中使用Converter转换器,它可以进行任意类型的转换。Spring MVC提供了很多的Converter转换器,但在特殊情况下需要自定义Converter转换器(如日期数据绑定)。
Spring MVC中有一些默认支持的类型,这些类型可以直接在Controller类的方法中定义,在参数绑定的过程中遇到该种类型就直接进行绑定。其默认支持的类型有以下几种:HttpServletRequest、HttpServletResponse、HttpSession及Model/ModelMap。
HttpServletRequest可以通过request对象获取请求信息。
HttpServletResponse可以通过response对象处理响应信息。
HttpSession可以通过session对象得到session中存放的对象。
Model是一个接口,ModelMap是一个接口实现,它的作用就是讲model数据填充到request域。
在Spring MVC中还可以自定义简单类型,这些类型也是直接在Controller类的方法中定义,在处理key/value信息时,就会以key名寻找Controller类的方法中具有相同名称的形参并进行绑定。
例如下面的例子,根据水果商品的id来获取水果的详细信息,具体Controller代码如下:
package com.ccff.controller;
import com.ccff.model.Fruits;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
//基于注解的Handler类
//使用 @Controller来标识它是一个控制器
@Controller
@RequestMapping("/query")
public class FruitsController {
private FruitsService fruitsService = new FruitsService();
//根据水果id查询水果信息
@RequestMapping(value = "/queryFruitByID.action", method = RequestMethod.GET)
public String queryFruitByID(Model model, int id){
//模拟service获取水果商品列表
List<Fruits> fruitsList = fruitsService.queryFruitsList();
List<Fruits> resultFruit = new ArrayList<>();
for (Fruits fruits : fruitsList){
if (fruits.getId() == id)
resultFruit.add(fruits);
}
//将结果放到model中传到显示页面中
model.addAttribute("resultFruit",resultFruit);
return "fruits/resultFruitList";
}
}
class FruitsService{
public List<Fruits> queryFruitsList(){
List<Fruits> fruitsList = new ArrayList<Fruits>();
Fruits apple = new Fruits();
apple.setId(1);
apple.setName("红富士苹果");
apple.setPrice(2.3);
apple.setProducing_area("山东");
Fruits banana = new Fruits();
banana.setId(2);
banana.setName("香蕉");
banana.setPrice(1.5);
banana.setProducing_area("上海");
fruitsList.add(apple);
fruitsList.add(banana);
return fruitsList;
}
}
可以在执行queryFruitByID请求时,为其指定一个参数id。由于通过RequestMapping的method属性指定了请求类型为GET类型,所以把项目部署在Tomcat上后,可以使用下面的URL请求:http://localhost:8080/demo/query/queryFruitByID.action?id=1就可以获得id为1的水果商品的详细信息,结果如图所示:
当然,如果参数名字不为“id”,绑定就不会成功,不过可以通过使用注解的方式为请求参数指定别名。注解@RequestParam可以对自定义简单类型的参数进行绑定,即如果使用@RequestParam,就无须设置Controller方法的形参名称与request传入的参数名称一致。而不使用@RequestParam时,就需要Controller方法的形参名称与request传入的参数名称一致,这样才能够绑定成功。
假设执行queryFruitByID请求时,传入的id属性名称为fruit_id,而Java代码使用的是驼峰命名,那么可以通过@RequestParam注解来指定绑定名称,而在形参中继续使用驼峰命名,Controller具体代码如下:
package com.ccff.controller;
import com.ccff.model.Fruits;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
//基于注解的Handler类
//使用 @Controller来标识它是一个控制器
@Controller
@RequestMapping("/query")
public class FruitsController {
private FruitsService fruitsService = new FruitsService();
//根据水果id查询水果信息
@RequestMapping(value = "/queryFruitByID.action", method = RequestMethod.GET)
public String queryFruitByID(Model model, @RequestParam(value = "fruit_id") int fruitId){
//模拟service获取水果商品列表
List<Fruits> fruitsList = fruitsService.queryFruitsList();
List<Fruits> resultFruit = new ArrayList<>();
for (Fruits fruits : fruitsList){
if (fruits.getId() == fruitId)
resultFruit.add(fruits);
}
//将结果放到model中传到显示页面中
model.addAttribute("resultFruit",resultFruit);
return "fruits/resultFruitList";
}
}
class FruitsService{
public List<Fruits> queryFruitsList(){
List<Fruits> fruitsList = new ArrayList<Fruits>();
Fruits apple = new Fruits();
apple.setId(1);
apple.setName("红富士苹果");
apple.setPrice(2.3);
apple.setProducing_area("山东");
Fruits banana = new Fruits();
banana.setId(2);
banana.setName("香蕉");
banana.setPrice(1.5);
banana.setProducing_area("上海");
fruitsList.add(apple);
fruitsList.add(banana);
return fruitsList;
}
}
再次将项目部署到Tomcat上后,在浏览器输入请求URL:http://localhost:8080/demo/query/queryFruitByID.action?fruit_id=1 ,可得如下结果,证明正确。
当Controller方法有多个形参时,如果请求中不包含其中的某个形参,此时是不会报错的,所以使用该参数时要进行空校验。如果要求绑定的参数一定不能为空,可以使用@RequestParam注解中的required属性来指定该形参是否必须传入,required属性为“true”指定参数必须传入。
修改Controller中的代码如下:
package com.ccff.controller;
import com.ccff.model.Fruits;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
//基于注解的Handler类
//使用 @Controller来标识它是一个控制器
@Controller
@RequestMapping("/query")
public class FruitsController {
private FruitsService fruitsService = new FruitsService();
//根据水果id查询水果信息
@RequestMapping(value = "/queryFruitByID.action", method = RequestMethod.GET)
public String queryFruitByID(Model model, @RequestParam(value = "fruit_id", required = true) int fruitId){
//模拟service获取水果商品列表
List<Fruits> fruitsList = fruitsService.queryFruitsList();
List<Fruits> resultFruit = new ArrayList<>();
for (Fruits fruits : fruitsList){
if (fruits.getId() == fruitId)
resultFruit.add(fruits);
}
//将结果放到model中传到显示页面中
model.addAttribute("resultFruit",resultFruit);
return "fruits/resultFruitList";
}
}
class FruitsService{
public List<Fruits> queryFruitsList(){
List<Fruits> fruitsList = new ArrayList<Fruits>();
Fruits apple = new Fruits();
apple.setId(1);
apple.setName("红富士苹果");
apple.setPrice(2.3);
apple.setProducing_area("山东");
Fruits banana = new Fruits();
banana.setId(2);
banana.setName("香蕉");
banana.setPrice(1.5);
banana.setProducing_area("上海");
fruitsList.add(apple);
fruitsList.add(banana);
return fruitsList;
}
}
在上面的例子,如果请求中没有包含fruit_id这个参数,直接请求URL:http://localhost:8080/demo/query/queryFruitByID.action ,则会报下图所示错误:
在Controller方法的形参中,如果有一些参数可能为空,但是又期望它们为空时有一个默认值,此时可以使用@RequestParam注解中的defaultValue属性来指定某些参数的默认值。
如将之前的Controller中queryFruitByID方法中的fruitId参数设置为空时的默认值为1,具体代码如下:
package com.ccff.controller;
import com.ccff.model.Fruits;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
//基于注解的Handler类
//使用 @Controller来标识它是一个控制器
@Controller
@RequestMapping("/query")
public class FruitsController {
private FruitsService fruitsService = new FruitsService();
//根据水果id查询水果信息
@RequestMapping(value = "/queryFruitByID.action", method = RequestMethod.GET)
public String queryFruitByID(Model model, @RequestParam(value = "fruit_id", defaultValue = "1") int fruitId){
//模拟service获取水果商品列表
List<Fruits> fruitsList = fruitsService.queryFruitsList();
List<Fruits> resultFruit = new ArrayList<>();
for (Fruits fruits : fruitsList){
if (fruits.getId() == fruitId)
resultFruit.add(fruits);
}
//将结果放到model中传到显示页面中
model.addAttribute("resultFruit",resultFruit);
return "fruits/resultFruitList";
}
}
class FruitsService{
public List<Fruits> queryFruitsList(){
List<Fruits> fruitsList = new ArrayList<Fruits>();
Fruits apple = new Fruits();
apple.setId(1);
apple.setName("红富士苹果");
apple.setPrice(2.3);
apple.setProducing_area("山东");
Fruits banana = new Fruits();
banana.setId(2);
banana.setName("香蕉");
banana.setPrice(1.5);
banana.setProducing_area("上海");
fruitsList.add(apple);
fruitsList.add(banana);
return fruitsList;
}
}
在上面的例子中,如果请求中没哟fruit_id参数,或者id参数值为空,此时处理器适配器会使用参数绑定组件将fruit_id的默认值defaultValue(为1)取出赋给形参fruitId。将项目部署到Tomcat上后,在浏览器直接请求如下URL:http://localhost:8080/demo/query/queryFruitByID.action 或 http://localhost:8080/demo/query/queryFruitByID.action?fruit_id= ,均可以得到id值为1的水果商品详细信息,结果如下图所示:
在开发过程中,Web端可以接受并绑定绑定包装类型的数据,在Service业务层稍微处理之后,交给DAO数据处理层处理。当Web端是由Spring MVC前端控制框架来控制时,Controller方法除了可以映射简单类型的参数外,还可以映射前台页面中包含的包装类型参数。
为了更好的理解Spring MVC处理包装类型的方式,我们接下来实现一个通过水果名和水果产地来搜索水果商品信息这样的一个功能。首先,在web/WEB-INF/jsp/fruits路径下添加名为“findFruits.jsp”的JSP页面,其中有名称、产地的搜索框和搜索按钮,搜索结果在下面以table列表形式显示,具体代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>水果搜索title>
head>
<body>
<h3>水果搜索h3>
<form action="queryFruitsByCondition.action" method="post">
名称:<input type="text" name="name" />
产地:<input type="text" name="producing_area" />
<input type="submit" value="搜索" />
form>
<hr/>
<h3>水果搜索结果h3>
<table width="300px;" border=1>
<tr>
<td>编号td>
<td>名称td>
<td>价格td>
<td>产地td>
tr>
<c:if test="${findList==null}">
<b>水果商品信息为空!b>
c:if>
<c:forEach items="${findList}" var="fruit">
<tr>
<td>${fruit.id }td>
<td>${fruit.name }td>
<td>${fruit.price }td>
<td>${fruit.producing_area }td>
tr>
c:forEach>
table>
body>
html>
可以看到,搜索区域是包裹在form表单中的,其中要请求的action地址为要在Controller中编写的模糊搜索方法对应的URL“queryFruitsByCondition.action”,而搜索条件的input中,可以看到name指定的名称为Fruits包装类中的属性名,这种类型将会被Spring MVC的处理器适配器解析,它会创建具体的实体类,并将相关的属性值通过set方法绑定到包装类中。
在controller包中创建名为“FindController”的类,然后给该类添加代表控制器的注解@Controller,然后编写名为“queryFruitsByCondition”的方法,并指定方法参数为Fruits实体类,由于是模糊搜索,所以返回多个搜索结果,是一个list集合。方法中的逻辑就是将从前端页面传来的Fruits实体类,传递给Service的模糊查询方法,得到结果。
Fruits模型类的具体代码如下:
package com.ccff.model;
public class Fruits {
private int id; //水果编号
private String name; //水果名
private double price; //水果价格
private String producing_area; //水果产地
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String getProducing_area() {
return producing_area;
}
public void setProducing_area(String producing_area) {
this.producing_area = producing_area;
}
}
Controller类的具体代码如下:
package com.ccff.controller;
import com.ccff.model.Fruits;
import com.ccff.service.FruitsService;
import com.ccff.service.FruitsServiceImpl;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@Controller
@RequestMapping("/query")
public class FindController {
private FruitsService fruitsService = new FruitsServiceImpl();
@RequestMapping("queryFruitsByCondition")
public String queryFruitsByCondition(Model model, Fruits fruits){
List<Fruits> findList = null;
if (fruits == null || (fruits.getName() == null && fruits.getProducing_area() == null)){
//如果fruits或查询条件为空,默认查询所有数据
findList = fruitsService.queryFruitsList();
}else {
//如果fruits查询条件不为空,按条件查询
findList = fruitsService.queryFruitsByCondition(fruits);
}
//将model数据传到页面
model.addAttribute("findList",findList);
return "fruits/findFruits";
}
}
在类名及queryFruitsByCondition方法上,分别使用了@RequestMapping注解,指定了请求响应,URL为“/query/queryFruitsByCondition”(action后缀在web.xml中设置)。在该方法中设置了两个参数,一个是返回视图数据的Model对象,一个是接收前端页面绑定的实体类对象(封装了form表单中的input标签中的参数)。通过形参活动Fruits实体对象,然后根据Fruits的空值情况判断调用不同的Service方法来获取不同的数据。最终返回了一个JSP视图的路径。
另外,这里将Service从之前的测试类中抽象出来,分为接口和实现。
Service层接口代码如下:
package com.ccff.service;
import com.ccff.model.Fruits;
import java.util.List;
public interface FruitsService {
public List<Fruits> queryFruitsList();
public Fruits queryFruitById(int id);
public List<Fruits> queryFruitsByCondition(Fruits fruits);
}
Service层接口实现类代码如下:
package com.ccff.service;
import com.ccff.model.Fruits;
import java.util.ArrayList;
import java.util.List;
public class FruitsServiceImpl implements FruitsService {
public List<Fruits> fruitsList = null;
/**
* 初始化水果商品水果数据
* @return
*/
public List<Fruits> initFruitsData(){
if (fruitsList == null){
fruitsList = new ArrayList<Fruits>();
Fruits apple = new Fruits();
apple.setId(1);
apple.setName("红富士苹果");
apple.setPrice(2.3);
apple.setProducing_area("山东");
Fruits banana = new Fruits();
banana.setId(2);
banana.setName("香蕉");
banana.setPrice(1.5);
banana.setProducing_area("上海");
fruitsList.add(apple);
fruitsList.add(banana);
return fruitsList;
}else {
return fruitsList;
}
}
/**
* 获取所有水果商品数据
* @return
*/
@Override
public List<Fruits> queryFruitsList() {
initFruitsData();
return fruitsList;
}
/**
* 根据给出的水果商品的id查询获取该水果商品的详细信息
* @param id
* @return
*/
@Override
public Fruits queryFruitById(int id) {
initFruitsData();
for (Fruits fruits : fruitsList){
if (fruits.getId() == id)
return fruits;
}
return null;
}
/**
* 根据给出的搜索条件构造Fruits实体类,搜索水果商品详细信息
* @param fruits
* @return
*/
@Override
public List<Fruits> queryFruitsByCondition(Fruits fruits) {
initFruitsData();
String name = fruits.getName();
String area = fruits.getProducing_area();
List<Fruits> queryList = new ArrayList<Fruits>();
Fruits f;
for (int i = 0; i < fruitsList.size(); i++) {
f = fruitsList.get(i);
//有一项符合条件就返回
if ((!"".equals(name) && f.getName().contains(name)) ||
(!"".equals(area) && f.getProducing_area().contains(area)))
queryList.add(f);
}
return queryList.size()>0?queryList:null;
}
}
将项目发布到Tomcat上后,在浏览器输入请求URL:http://localhost:8080/demo/query/queryFruitsByCondition.action ,第一次由于搜索框中输入的内容为空,即由前台页面传递给Controller的Fruits实体类为空,因此默认显示所有水果商品的信息,如下图所示:
当在搜索框中输入水果名为“苹果”时,点击搜索按钮,则结果如下图所示,说明包装类型的Fruits参数已经成功与Controller方法中的形参绑定:
在包装类中嵌套包装类,即Java实体类中包含其他其他实体类,此时Spring MVC依然可以解析并成功绑定该类型的包装类。
例如查询用户姓名为“张三”,并且其名下的水果商品中产地包含“山东”的商品,此时就需要这样一个JavaBean来作为查询包装类UserAndProductModel,具体代码如下:
package com.ccff.model;
public class UserAndProductModel {
private User user;
private Fruits userFruits;
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Fruits getUserFruits() {
return fruits;
}
public void setUserFruits(Fruits fruits) {
this.fruits = fruits;
}
}
UserAndProductModel实体类中又涉及到了另外两个实体类,分别为:Fruits和User实体类。Fruits实体类之前已经给出了详细定义,此处便仅给出User实体类的详细定义,具体代码如下:
package com.ccff.model;
public class User {
private int userId;
private String username;
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
然后对之前的名为“findFruits.jsp”的JSP页面中的form表单进行修改。由于该查询中包装类包括了User类和水果商品Fruits类作为其属性,那么在进行查询时,指定input的name属性为“包装对象.属性”的形式。例如在下面的修改中,仅对水果产品信息进行查询,同时也可以对用户数据进行查询,它们会同时被绑定在 UserAndProductModel 对象中。具体代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>水果搜索title>
head>
<body>
<h3>水果搜索h3>
<form action="queryUserAndFruitsByCondition.action" method="post">
名称:<input type="text" name="userFruits.name" />
产地:<input type="text" name="userFruits.producing_area" />
<input type="submit" value="搜索" />
form>
<hr/>
<h3>水果搜索结果h3>
<table width="300px;" border=1>
<tr>
<td>编号td>
<td>名称td>
<td>价格td>
<td>产地td>
tr>
<c:if test="${findList==null}">
<b>水果商品信息为空!b>
c:if>
<c:forEach items="${findList}" var="fruit">
<tr>
<td>${fruit.id }td>
<td>${fruit.name }td>
<td>${fruit.price }td>
<td>${fruit.producing_area }td>
tr>
c:forEach>
table>
body>
html>
对JSP页面修改完毕后,接着,为了在Controller中拿到该类型,只需要使用UserAndProductModel作为其方法的形参即可,(前端页面中的表单input标签的name属性值的名与形参对象中的属性名可需要保持一致),具体代码如下:
package com.ccff.controller;
import com.ccff.model.Fruits;
import com.ccff.model.UserAndProductModel;
import com.ccff.service.FruitsService;
import com.ccff.service.FruitsServiceImpl;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.List;
@Controller
@RequestMapping("/query")
public class UserAndFruitsController {
private FruitsService fruitsService = new FruitsServiceImpl();
@RequestMapping("/queryUserAndFruitsByCondition")
public String queryUserAndFruitsByCondition(Model model, UserAndProductModel userAndProductModel){
List<Fruits> findList = null;
if (userAndProductModel == null){
findList = fruitsService.queryFruitsList();
}else if (userAndProductModel.getUserFruits() == null || (userAndProductModel.getUserFruits().getName() == null && userAndProductModel.getUserFruits().getProducing_area() == null)){
//如果fruits或查询条件为空,默认查询所有数据
findList = fruitsService.queryFruitsList();
}else {
//如果fruits查询条件不为空,按条件查询
findList = fruitsService.queryFruitsByCondition(userAndProductModel.getUserFruits());
}
//将model数据传到页面
model.addAttribute("findList",findList);
return "fruits/findFruits";
}
}
当前端页面发出请求后,处理器适配器会解析这种格式的name,将该参数当做查询包装类的成员参数绑定起来,作为Controller方法的形参。这样在Controller方法中就可以通过查询包装类获取其包装类的其他类的对象。
再次将项目部署到Tomcat上后,在浏览器上访问请求URL:http://localhost:8080/demo/query/queryUserAndFruitsByCondition.action ,同理第一次由于查询条件为空则显示所有水果信息。当在产地搜索框中输入“山东”后点击搜索按钮,则可以搜索出产地为 山东的所有水果信息,具体如图所示:
有时候前端请求的数据是批量的,此时就要求Web端去处理请求时,获取这些批量的请求参数。一般批量的请求参数在Java中是以数组或者集合的形式接收的,而Spring MVC提供了接收和解析数据和集合参数类型的机制。当前端请求的参数为批量数据时,处理器适配器会根据批量数据的类型,以及Controller的形参定义的类型,进行数据绑定,使得前端请求数据绑定到相应的数组或集合参数上。
第一步,首先在名为“collectionParamTest.jsp”的JSP页面上设置一个复选框表单,让榕湖选择一个或多个水果商品数据进行操作,具体JSP页面代码如下图所示:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>集合类型参数绑定Demotitle>
head>
<body>
<h3>水果列表h3>
<form action="showParam.action" method="post">
<table width="300px;" border=1>
<tr>
<td>选择td>
<td>编号td>
<td>名称td>
<td>价格td>
<td>产地td>
tr>
<c:if test="${fruitsList==null}">
<b>水果商品信息为空!b>
c:if>
<c:forEach items="${fruitsList}" var="fruit">
<tr>
<td><input type="checkbox" name="fruitsIdArray" value="${fruit.id}" />td>
<td>${fruit.id }td>
<td>${fruit.name }td>
<td>${fruit.price }td>
<td>${fruit.producing_area }td>
tr>
c:forEach>
table>
<br/>
<input type="submit" value="批量测试提交" />
form>
body>
html>
由上面的JSP代码我们可以看出,一个或多个被选中的input控件的name属性值是相同的,这就需要在Web端使用一个name相同的数组类型的参数去接收批量参数。因此,在Controller中的showParam方法中使用了一个名为fruitsIdArray的形参去接收批量请求参数,而showPage方法则是为了显示之前的JSP页面。Controller具体代码如下所示:
package com.ccff.controller;
import com.ccff.model.Fruits;
import com.ccff.service.FruitsService;
import com.ccff.service.FruitsServiceImpl;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
@Controller
@RequestMapping("/collectionTest")
public class CollectionController {
private FruitsService fruitsService = new FruitsServiceImpl();
@RequestMapping("/showPage")
public String showPage(Model model){
List<Fruits> fruitsList = fruitsService.queryFruitsList();
model.addAttribute("fruitsList",fruitsList);
return "fruits/collectionParamTest";
}
@RequestMapping("/showParam")
public void showParam(int[] fruitsIdArray, HttpServletResponse response) throws IOException {
for (int i = 0; i < fruitsIdArray.length; i++) {
System.out.println("fruitsIdArray["+i+"] = "+fruitsIdArray[i]);
response.getWriter().write("fruitsIdArray["+i+"] = "+fruitsIdArray[i]+"\n");
}
}
}
将项目部署到Tomcat上后,在浏览器输入请求URL:http://localhost:8080/demo/collectionTest/showPage.action 后即可显示所有水果商品信息列表。商品信息页面如下图所示:
当选择所有的复选框后,单击“批量测试提交”按钮后,表单提交给showParam.action处理后在页面直接显示所选的水果商品id,结果如下图所示,说明通过数组形式成功绑定了前端传递的批量数据。
总结:form表单的多选控件的name属性要和Controller相关方法的形参保持一致。
当想把页面上的批量数据通过Spring MVC转换为Web端的List类型的对象时,若我们直接在Controller中的方法的形参中设置参数“List list”时,这样的方式是不行的,会报 Could not instantiate bean class [java.util.List]: Specified class is an interface 这个异常。错误如下:
之所以会出现以上异常,是因为Spring MVC中获取参数的方式不管有多少种,他的本质依然是调用如下的代码:
request.getParameter("name");
那把这个参数封装到一个对象中,也只能是同setter方法,那问题的关键是如何找到这个setter方法?肯定是setName中的name和request中的name对应。这才能找到。你想,如果你单纯接收一个list参数,list虽然有get和set方法,但是没有名字呀,只能根据数组下标来判断参数位置。
解决这个问题有以下三种方法:
方法一,对于集合的泛型是基本数据类型或String类型,其实可以采用数组的形式进行参数的接收,然后将数组类型的形参转为泛型为基本数据类型或String类型的集合参数。
方法二,其实不是不可以在Controller的方法中直接设置形参的类型为集合类型,只不过需要稍加修改。Spring MVC在接收集合请求参数时,需要在Controller方法的集合参数里前添加@RequestBody,而@RequestBody默认接收的enctype (MIME编码)是application/json,因此发送POST请求时需要设置请求报文头信息,否则Spring MVC在解析集合请求参数时不会自动的转换成JSON数据再解析成相应的集合。具体链接请参考他人博客:SpringMVC接收复杂集合参数,集合对象
方法三,这里着重介绍一种普适的解决办法。创建一个Vo类,包装需要传递的参数。即使用一个包装类(例如名为“ListQueryModel”)来接受前台传来的List集合数据,具体代码如下:
package com.ccff.model;
import java.util.List;
public class ListQueryModel {
private List<Fruits> fruitsList;
private List<Integer> fruitsIdList;
public List<Fruits> getFruitsList() {
return fruitsList;
}
public void setFruitsList(List<Fruits> fruitsList) {
this.fruitsList = fruitsList;
}
public List<Integer> getFruitsIdList() {
return fruitsIdList;
}
public void setFruitsIdList(List<Integer> fruitsIdList) {
this.fruitsIdList = fruitsIdList;
}
}
将页面中每一组数据的input控件的name属性使用“集合名[下标].属性(对于基本数据类型而言,.属性不用写)”的形式,当请求传递到Web端时,处理器适配器会根据name的格式将请求参数解析为相应的List集合。前台的JSP代码如下所示:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>集合类型参数绑定Demotitle>
head>
<body>
<h3>水果列表h3>
<%--演示泛型为基本数据类型或String类型的List类型的请求参数开始--%>
<hr/>
<h4>演示泛型为基本数据类型或String类型的List类型的请求参数h4>
<form action="showBasicListParam.action" method="post">
<table width="300px;" border=1>
<tr>
<td>选择td>
<td>编号td>
<td>名称td>
<td>价格td>
<td>产地td>
tr>
<c:if test="${fruitsList==null}">
<b>水果商品信息为空!b>
c:if>
<c:forEach items="${fruitsList}" var="fruit" varStatus="status">
<tr>
<td><input type="checkbox" name="fruitsIdList[${status.index}]" value="${fruit.id}" />td>
<td>${fruit.id}td>
<td>${fruit.name}td>
<td>${fruit.price}td>
<td>${fruit.producing_area}td>
tr>
c:forEach>
table>
<br/>
<input type="submit" value="基本数据类型批量测试提交" />
form>
<%--演示泛型为基本数据类型或String类型的List类型的请求参数结束--%>
<%--演示泛型为引用类型的List类型的请求参数开始--%>
<hr/>
<h4>演示泛型为引用类型的List类型的请求参数h4>
<form action="showListParam.action" method="post">
<table width="300px;" border=1>
<tr>
<td>编号td>
<td>名称td>
<td>价格td>
<td>产地td>
tr>
<c:if test="${fruitsList==null}">
<b>水果商品信息为空!b>
c:if>
<c:forEach items="${fruitsList}" var="fruit" varStatus="status">
<tr>
<td><input name="fruitsList[${status.index}].id" value="${fruit.id}" />td>
<td><input name="fruitsList[${status.index}].name" value="${fruit.name}" />td>
<td><input name="fruitsList[${status.index}].price" value="${fruit.price}" />td>
<td><input name="fruitsList[${status.index}].producing_area" value="${fruit.producing_area}" />td>
tr>
c:forEach>
table>
<br/>
<input type="submit" value="引用类型批量测试提交" />
form>
<%--演示泛型为引用类型的List类型的请求参数结束--%>
body>
html>
在Controller中设置两个方法分别处理泛型为基本数据类型或String类型的List类型请求参数的方法showBasicListParam和处理泛型为引用类型的List类型请求参数的方法showListParam。具体代码如下:
package com.ccff.controller;
import com.ccff.model.Fruits;
import com.ccff.model.ListQueryModel;
import com.ccff.service.FruitsService;
import com.ccff.service.FruitsServiceImpl;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
@Controller
@RequestMapping("/collectionTest")
public class CollectionController {
private FruitsService fruitsService = new FruitsServiceImpl();
@RequestMapping("/showPage")
public String showPage(Model model){
List<Fruits> fruitsList = fruitsService.queryFruitsList();
model.addAttribute("fruitsList",fruitsList);
return "fruits/collectionParamTest";
}
@RequestMapping("/showListParam")
public void showListParam(ListQueryModel listQueryModel, HttpServletResponse response) throws IOException {
List<Fruits> list = listQueryModel.getFruitsList();
//这句话的意思,是让浏览器用utf8来解析返回的数据
response.setHeader("Content-type", "text/html;charset=UTF-8");
//这句话的意思,是告诉servlet用UTF-8转码,而不是用默认的ISO8859
response.setCharacterEncoding("UTF-8");
for (int i = 0; i < list.size(); i++) {
response.getWriter().write("水果商品名称:"+list.get(i).getName()+"
");
}
}
@RequestMapping("/showBasicListParam")
public void showBasicListParam(ListQueryModel listQueryModel, HttpServletResponse response) throws IOException {
List<Integer> list = listQueryModel.getFruitsIdList();
//这句话的意思,是让浏览器用utf8来解析返回的数据
response.setHeader("Content-type", "text/html;charset=UTF-8");
//这句话的意思,是告诉servlet用UTF-8转码,而不是用默认的ISO8859
response.setCharacterEncoding("UTF-8");
for (int i = 0; i < list.size(); i++) {
response.getWriter().write("水果商品ID:"+list.get(i)+"
");
}
}
}
将项目部署到Tomcat上后,在浏览器内输入请求URL:http://localhost:8080/demo/collectionTest/showPage.action ,显示内容如下图所示,同时分别做以下两个测试:
测试一:在演示泛型为基本数据类型或String类型的List类型的请求参数的表单中,勾选全部水果商品后,点击“基本数据类型批量测试提交”按钮,表单将勾选的水果商品的id数据提交给Controller的showBasicListParam方法处理,处理后的结果如下则证明测试成功。
测试二:在泛型为引用类型的List类型的请求参数的表单中直接点击“基本数据类型批量测试提交”按钮,表单将全部水果商品数据提交给Controller的showListParam方法处理,处理后的结果如下则证明测试成功。
注意:form表单的集合元素的name属性要和Controller相关方法的List形参对象名称保持一致。
当想把页面上的批量数据通过Spring MVC转换为Web端的Map类型的对象时,每一组数据的input控件的name属性使用“Map名[‘key值’]”的形式。当请求传递到Web端时,处理器适配器会根据name的格式将请求参数解析为相应的Map集合。
前端JSP页面代码如下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>集合类型参数绑定Demotitle>
head>
<body>
<h3>水果列表h3>
<hr/>
<h4>演示Map类型的请求参数h4>
<%--演示Map类型的请求参数开始--%>
<form action="showMapParam.action" method="post">
<table width="300px;" border=1>
<tr>
<td>编号td>
<td>名称td>
<td>价格td>
<td>产地td>
tr>
<tr>
<td><input name="fruitsMap['id']" value="3" />td>
<td><input name="fruitsMap['name']" value="凤梨" />td>
<td><input name="fruitsMap['price']" value="5.7" />td>
<td><input name="fruitsMap['producing_area']" value="广州" />td>
tr>
table>
<br/>
<input type="submit" value="Map类型批量测试提交" />
form>
<%--演示Map类型的请求参数结束--%>
body>
html>
与List类型的集合请求参数类似,这里也需要使用一个包装类对Map类型的参数进行封装。在model中创建名为“MapQueryModel”的类作为接收请求参数的对象。在MapQueryModel包装类中定义了映射用的Map属性,其名称与“Map名[‘key值’]”形式中的Map名保持一致,具体代码如下所示:
package com.ccff.model;
import java.util.Map;
public class MapQueryModel {
private Map<String,Object> fruitsMap;
public Map<String, Object> getFruitsMap() {
return fruitsMap;
}
public void setFruitsMap(Map<String, Object> fruitsMap) {
this.fruitsMap = fruitsMap;
}
}
之前的JSP页面中,每一个input的参数都使用了“Map名[‘key值’]”形式,这种形式的数据提交时会被处理器适配器解析为Controller对应的方法含有相同Map名的Map类型属性的包装类参数。具体的Controller代码如下所示:
package com.ccff.controller;
import com.ccff.model.Fruits;
import com.ccff.model.ListQueryModel;
import com.ccff.model.MapQueryModel;
import com.ccff.service.FruitsService;
import com.ccff.service.FruitsServiceImpl;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@Controller
@RequestMapping("/collectionTest")
public class CollectionController {
private FruitsService fruitsService = new FruitsServiceImpl();
@RequestMapping("/showPage")
public String showPage(Model model){
List<Fruits> fruitsList = fruitsService.queryFruitsList();
model.addAttribute("fruitsList",fruitsList);
return "fruits/collectionParamTest";
}
@RequestMapping("/showMapParam")
public void showMapParam(MapQueryModel mapQueryModel, HttpServletResponse response) throws IOException {
Map<String,Object> map = mapQueryModel.getFruitsMap();
//这句话的意思,是让浏览器用utf8来解析返回的数据
response.setHeader("Content-type", "text/html;charset=UTF-8");
//这句话的意思,是告诉servlet用UTF-8转码,而不是用默认的ISO8859
response.setCharacterEncoding("UTF-8");
for (String key : map.keySet()){
String string = "fruitsMap["+key+"] = "+map.get(key);
response.getWriter().write(string+"
");
}
}
}
将项目部署到Tomcat上后,在浏览器输入请求URL:http://localhost:8080/demo/collectionTest/showPage.action 后,即可显示需要提交的水果商品数据,具体如下图所示:
当直接点击“Map类型批量测试提交”后,表单将要提交的水果商品数据通过POST的方式提交给Controller中的showMapParam方法处理,处理结果如下图所示则证明正确:
注意:form表单的Map元素的name属性要和Controller相关方法的Map形参名,以及对应的key键保持一致。