数据绑定概述
在执行程序时,SpringMVC根据客户端请求参数的不同将请求消息中的消息以一定的方式转换并绑定到控制器类的方法参数中。
在数据绑定过程中,SpringMVC会通过数据绑定组件(DataBinder)将请求参数串的内容进行类型转换,然后将转换后的值赋给控制器类中方法的形参。具体处理步骤如下:
- Spring MVC将ServletRequest对象传递给DataBinder
- 将处理方法的入参对象传递给DataBinder
- DataBinder调用ConversionService组件进行数据类型转换、数据格式化等工作,并将ServletRequest对象中的消息填充到参数对象中。
- 调用Validator组件对已经绑定了请求消息数据的参数对象进行数据合法性校验。
- 校验完成后会生成数据绑定结果BindingResult对象,SpringMVC会将BindingResult对象中的内容赋给处理方法的相应参数。
根据客户端请求参数类型和个数的不同,将SpringMVC中的数据绑定主要分为简单数据绑定和复杂数据绑定。
项目的目录结构如下:
简单数据绑定
简单数据绑定包括绑定默认数据类型、绑定简单数据类型、绑定POJO类型、绑定包装POJO等。
绑定默认数据类型
常用的默认参数类型如下:
- HttpServletRequest:通过request对象获取请求消息。
- HttpServletResponse:通过response对象处理响应信息。
- HttpSession:通过session对象得到session中存储的对象
- Model/ModelMap:Model是一个接口,ModelMap是一个接口实现,作用是将Model数据填充到request域。
代码实现
下面以HttpServletRequest类型的使用为例:
1.创建一个Maven项目,并在pom.xml中添加相关的坐标,代码如下:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- 版本锁定 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.6.RELEASE</version> </dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
2.在web.xml中配置SpringMVC的前端控制器等消息。代码如下:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>Archetype Created Web Application</display-name>
<servlet>
<!--配置前端过滤器-->
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--初始化时加载配置文件-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-config.xml</param-value>
</init-param>
<!--表示容器来启动时立即加载Servlet-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
3.在springmvc-config.xml中配置组件扫描器和视图解析器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--定义扫描器-->
<context:component-scan base-package="com.ssm.controller"/>
<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
4.编写控制类UserContoller
package com.ssm.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
@Controller
public class UserController {
@RequestMapping("/selectUser")
public String handleRequest(HttpServletRequest request) throws Exception {
//获取请求地址中的参数id的值
String id = request.getParameter("id");
System.out.println("id="+id);
return "success";
}
}
在方法中使用了HttpServletRequest对象的getParameter()方法获取指定的参数,并返回了一个名为“success”的视图,SpringMVC会通过视图解析器在"WEB-INF/jsp"路径下寻找"success.jsp"文件。
5.success.jsp,该页面作为正确执行操作后的响应页面,没有其他业务逻辑,代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>入门程序</title>
</head>
<body>
OK! 执行成功.
</body>
</html>
6.启动项目,在浏览器中访问地址http://localhost:8081/selectUser?id=2,我们可以在控制台看到,后台方法已从请求地址最终正确地获取到了id的参数信息。
绑定简单数据类型
简单数据类型的绑定是指Java中几种基本数据类型的绑定,如int、String、Double等类型
我们只需要将控制器类UserController中的selectUser()方法的参数修改为使用简单数据类型的形式即可,代码如下:
@RequestMapping("/selectUser")
public String handleRequest(Integer id ) throws Exception {
System.out.println("id="+id);
return "success";
}
注意:
有时前台请求中参数名和后台控制器类方法中的形参名不一样,就会导致后台无法正确绑定并接收到前端请求的参数,我们可以通过Spring MVC提供的@RequestParam注解来进行间接数据绑定。
@RequestParam注解主要用于定义请求中的参数,属性参数如下所示:
@RequestParam注解的使用非常简单,假设浏览器的请求地址为http://localhost:8081/selectUser?user_id=2,那么在后台selectUser()方法中使用如下:
@RequestMapping("/selectUser")
public String handleRequest(@RequestParam("user_id") Integer id ) throws Exception {
System.out.println("id="+id);
return "success";
}
需要注意的是:@RequestParam中的value属性需与前台传递过来的参数名一致。
绑定POJO类型
POJO类型的数据绑定就是将所有关联的请求参数封装在一个POJO中,然后在方法中直接使用该POJO作为形参来完成数据绑定。
我们将使用一个用户注册案例来演示POJO类型数据的绑定
User.java
用来封装用户注册的消息参数
package com.ssm.po;
public class User {
private Integer id;
private String username;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
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.java
在控制器UserController类中编写向注册页面跳转和接收用户注册信息的方法,代码如下:
//向注册页面跳转
@RequestMapping("/toRegister")
public String toRegister(){
return "register";
}
//接收用户注册信息
@PostMapping("/registerUser")
public String registerUser(User user){
System.out.println("username="+user.getUsername());
System.out.println("password="+user.getPassword());
return "success";
}
register.jsp
在register.jsp编写用户注册表单,表单需要以POST方式提交,并且在提交时会发送一条以"registerUser"结尾的请求消息,代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>注册</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/registerUser" method="post">
用户名: <input type="text" name="username"> <br/>
密码: <input type="text" name="password"> <br/>
<input type="submit" value="注册">
</form>
</body>
</html>
注意: 在使用POJO类型数据绑定时,前端请求的参数名(本例中指form表单内各元素的name属性值)必须要与绑定的POJO类中的属性名一样,这样才会自动将请求数据绑定到POJO对象中,否则后台接收的参数值为null
启动项目,在浏览器访问地址"http://localhost:8081/toRegister",就会跳转到注册页面register.jsp
输入用户名和密码,点击注册,浏览器会跳转到"success.jsp"页面,此时控制台输出成功如下:
注意:如果我们在用户名输入中文的时候,虽然浏览器可以正确跳转到success.jsp页面,但是在控制台输出中文信息的时候会出现乱码,我们只需要在web.xml中配置Spring提供的过滤编码器就可以解决中文乱码问题,代码如下:
<!--配置编码过滤器-->
<filter>
<filter-name>CharacterEncodeingFilter</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>
</filter>
<filter-mapping>
<filter-name>CharacterEncodeingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
上述代码中,通过< filter-mapping >元素的配置会拦截前端页面中的所有请求,并交由名称为CharacterEncodingFilter的编码过滤器类进行处理,我们在< filter >元素中通过初始化参数设置统一的编码为UTF-8。这样所有的请求信息都会以UTF-8的编码格式进行解析。
绑定包装POJO
所谓包装POJO,就是在一个POJO中包含另一个简单POJO,例如,在学生对象中包含班级对象。这样在使用时,就可以通过学生查询到班级信息。
班级类Banji和学生类Student
Student类用于封装学生和班级信息。
Banji.java
package com.ssm.po;
public class Banji {
private Integer banji_id; //班级id
private String banji_name;//班级名
public Integer getBanji_id() {
return banji_id;
}
public void setBanji_id(Integer banji_id) {
this.banji_id = banji_id;
}
public String getBanji_name() {
return banji_name;
}
public void setBanji_name(String banji_name) {
this.banji_name = banji_name;
}
}
Student.java
package com.ssm.po;
public class Student {
private Integer stu_id;//学生id
private String stu_name;//学生姓名
private Banji banji; //学生所在班级
public Integer getStu_id() {
return stu_id;
}
public void setStu_id(Integer stu_id) {
this.stu_id = stu_id;
}
public String getStu_name() {
return stu_name;
}
public void setStu_name(String stu_name) {
this.stu_name = stu_name;
}
public Banji getBanji() {
return banji;
}
public void setBanji(Banji banji) {
this.banji = banji;
}
}
StudentContoller
在StudentContoller中编写一个跳转到学生查询页面的方法和一个查询学生及班级信息的方法,代码如下:
package com.ssm.controller;
import com.ssm.po.Student;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class StudentController {
//向学生查询页面跳转
@RequestMapping("/tofindStudentWithBanji")
public String tofindStudentWithBanji(){
return "Student";
}
//查询学生和班级信息
@PostMapping("/findStudentWithBanji")
public String findStudentWithBanji(Student student){
System.out.println("stu_id="+student.getStu_id());
System.out.println("banji_name="+student.getBanji().getBanji_name());
return "success";
}
}
Student.jsp
在页面中编写通过学生编号和所属班级作为查询条件来查询学生信息,代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>学生查询</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/findStudentWithBanji" method="post">
学生编号:<input type="text" name="stu_id"> <br/>
所属班级: <input type="text" name="banji.banji_name"><br/>
<input type="submit" value="查询">
</form>
</body>
</html>
注意:在使用包装POJO类型数据绑定时,前端请求的参数名编写必须符号以下两种情况:
- 如果查询条件参数是包装类的直接基本属性,参数名就直接用对应的属性名,如上述代码中的stu_id
- 如果查询条件参数是包装类中POJO的子属性,参数名就必须为【对象.属性】,其中【对象】要和包装POJO中的对象属性名称一致,【属性】要和包装POJO中的对象子属性一致,如上述代码中的banji.banji_name;
启动项目,访问地址http://localhost:8081/tofindStudentWithBanji,显示如下:
点击查询按钮后,浏览器会跳转到success.jsp,此时控制台输出为:
除了上述几种简单数据绑定外,有些特殊类型的参数无法在后台进行直接转换的,例如日期数据需要我们自定义转换器(Converter)或格式化(Formatter)进行数据绑定。
自定义类型转换器
我们自定义日期数据类型转换器
1.实现Converter接口
定义一个类,实现Converter接口,该接口有两个泛型
package com.ssm.converter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.util.StringUtils;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class StringToDateConverter implements Converter<String,Date>{
@Override
public Date convert(String s) {
DateFormat format = null;
try {
if(StringUtils.isEmpty(s)){
throw new NullPointerException("请输入要转换的日期");
}
format = new SimpleDateFormat("yyyy-MM-dd");
Date date = format.parse(s);
return date;
}catch ( Exception e){
throw new RuntimeException("输入日期有误");
}
return null;
}
}
2.配置类型转换器
在springmvc-config.xml中配置类型转换器,spring配置类型转换器的机制是:将自定义的转换器注册到类型转换服务中去,代码如下:
<!--配置类型转换器工厂-->
<bean id="converterService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<!--给工厂注入一个新的类型转换器-->
<property name="converters">
<array>
<!--配置自定义类型转换器-->
<bean class="com.ssm.converter.StringToDateConverter"></bean>
</array>
</property>
</bean>
3.在annotation-driven标签中引入配置的类型转换服务
<!--在annotation-driven标签中引用配置的类型转换服务-->
<mvc:annotation-driven conversion-service="converterService"/>
4.StudentController
在StudentContoller编写一个stringToDate的方法,代码如下:
@RequestMapping("/stringToDate")
public String stringToDate(Date date){
System.out.println(date);
return "success";
}
启动程序,访问地址“http://localhost:8081/stringToDate?date=2018-01-01”,可以在控制台中看见字符串已经转换为日期类型了。
复杂数据绑定
复杂数据绑定比如数据的绑定、集合的绑定。
绑定数组
在实际开发中,可能会遇到前端请求需要传递到后台一个或多个相同名称参数的情况(如批量删除),我们可以通过绑定数组的方式解决该类问题。
我们使用一个批量删除课程的例子来详细讲解绑定数据的操作
course.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>课程列表</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/deleteCourse" method="post">
<table>
<tr>
<td>选择</td>
<td>课程名</td>
</tr>
<tr>
<td><input name="ids" value="1" type="checkbox"></td>
<td>JAVA课程设计</td>
</tr>
<tr>
<td><input name="ids" value="2" type="checkbox"></td>
<td>MySQL数据库</td>
</tr>
<tr>
<td><input name="ids" value="3" type="checkbox"></td>
<td>JAVAEE应用开发</td>
</tr>
</table>
<input type="submit" value="删除">
</form>
</body>
</html>
在上述代码中,定义了3个name属性相同而value属性值不同的复选框,在点击删除的时候,会将选择的复选框中的value属性值提交给"/deleteCourse"请求中。
CourseController
在控制器类CourseController中编写接收批量删除课程的方法(和跳转到课程列表的页面),代码如下:
package com.ssm.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class CourseController {
@RequestMapping("/toCourse")
public String toCourse(){
return "course";
}
@PostMapping("/deleteCourse")
public String deleteCourse(Integer[] ids){
if(ids!=null){
//使用输出语句模拟已经删除的数据
for (Integer id : ids) {
System.out.println("删除了id为"+id+"的课程");
}
}else{
System.out.println("ids=null");
}
return "success";
}
}
启动项目,访问地址http://localhost:8081/toCourse,显示效果如下:
点击删除按钮后,会跳转到success.jsp页面,此时控制台的打印信息如下图所示:
绑定集合
绑定集合即在包装类中定义一个包含对象类的集合,然后再接收方法中将参数类型定义为该包装类的集合。
下面以批量修改用户为例讲解集合数据绑定的使用
UserVo.java
创建包装类UserVo来封装用户集合属性
package com.ssm.po;
import java.util.List;
public class UserVo {
private List<User> users; //用户列表
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
}
UserController
在控制器类 UserController中编写接收批量修改用户的方法,以及向用户修改页面跳转的方法,代码如下:
//向用户批量修改页面跳转
@RequestMapping("/toUserEdit")
public String toUserEdit(){
return "user_edit";
}
//接收批量修改用户的方法
@PostMapping("/editUsers")
public String editUsers(UserVo userList){
//获取所有用户的数据
List<User> users = userList.getUsers();
for (User user : users) {
if(user.getId()!=null){
System.out.println("修改了id为"+user.getId()+"的用户名为"+user.getUsername());
}
}
return "success";
}
注意:在使用集合数据绑定时,后台方法中不支持直接使用集合形参进行数据绑定,所以需要使用包装POJO作为参数,然后再包装POJO中包装一个集合属性。
user_edit.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>修改用户</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/editUsers" method="post">
<table>
<tr>
<td>选择</td>
<td>用户名</td>
</tr>
<tr>
<td><input name="users[0].id" value="1" type="checkbox"></td>
<td><input name="users[0].username" value="小鑫" type="text"></td>
</tr>
<tr>
<td><input name="users[1].id" value="2" type="checkbox"></td>
<td><input name="users[1].username" value="邵祎" type="text"></td>
</tr>
</table>
<input type="submit" value="修改">
</form>
</body>
</html>
启动项目,访问地址"http://localhost:8081/toUserEdit",页面效果如下:
点击修改按钮的时候,页面跳转到success.jsp,此时控制台以成功打印出批量修改的用户信息