目录:
- “数据绑定” 介绍
- 1.简单数据绑定 :
- 绑定 “默认数据” 类型
- 绑定 “简单数据类型” 类型 (绑定Java“基本数据类型”)
- 绑定 “POJO类型”
- 绑定 “包装 POJO”
- “自定义数据” 绑定 :
- Converter (自定义转换器)
作者简介 :一只大皮卡丘,计算机专业学生,正在努力学习、努力敲代码中! 让我们一起继续努力学习!
该文章参考学习教材为:
《Java EE企业级应用开发教程 (Spring + Spring MVC +MyBatis)》 黑马程序员 / 编著
文章以课本知识点 + 代码为主线,结合自己看书学习过程中的理解和感悟 ,最终成就了该文章文章用于本人学习使用 , 同时希望能帮助大家。
欢迎大家点赞👍 收藏⭐ 关注💖哦!!!(侵权可联系我,进行删除,如果雷同,纯属巧合)
在前面的知识点中,可以知道后台的请求处理方法可以包含多种参数类型。在实际的项目开发中,多数情况下 客户端会传递带有不同参数的请求,那么后台是如何绑定并获取这些请求参数的呢? 本章将对Spring MVC 框架中的 数据绑定 进行详细讲解。
“数据绑定” 介绍
在执行程序时,Spring MVC 会根据客户端请求参数的不同,将请求消息中的信息以一定的方式转换并绑定到控制器类的方法参数中。这种将 请求消息数据与后台方法参数建立连接的过程就是 Spring MVC 中的数据绑定。
( SpringMVC会对前端的“请求参数”进行转换且绑定到控制类的“方法参数”中 )在数据绑定过程中,Spring MVC 框架会通过数据绑定组件( DataBinder )将 请求参数串的内容进行类型转换,然后将转换后的值赋给控制器类中方法的形参,这样后台方法就可以正确绑定并获取客户端请求携带的参数了。
(SpringMVC框架通过 “数据绑定组件” : DataBinder来对前端的 “请求参数” 进行 转换 且 赋值到控制器类“方法参数”中 )SpringMVC “数据绑定” 过程如下图所示 :
(1) Spring MVC将ServletRequest对象 传递给 DataBinder (数据绑定组件)。
(2)将处理方法的入参对象 传递给DataBinder。
(3) DataBinder 调用 ConversionService组件进行数据类型转换、数据格式化等工作,并将ServletRequest对象中的消息填充到参数对象中。
(4)调用 Validator 组件对已经绑定了请求消息数据的参数对象进行数据合法性校验。
(5)校验完成后会生成数据绑定结果 :BindingResult 对象,Spring MVC会将 BindingResult对象 中的内容赋给处理方法的相应参数。根据客户端请求参数类型和个数的不同,我们将 Spring MVC 中的数据绑定主要分为 简单数据绑定 和 复杂数据绑定。
1.简单数据绑定 :
绑定 “默认数据” 类型
当前端请求的 参数比较简单 时,可以在后台方法的形参中直接使用Spring MVC提供的 默认参数类型 进行 数据绑定。
常用的 默认参数类型 如下 :(以HttpServletRequest类型为例进行演示)
“默认参数”类型 描述 HttpServletRequest 通过request对象获取请求信息。 HttpServletResponse 通过response 处理响应信息。 HttpSession 通过session 对象得到 session 中存储的对象。 Model / ModelMap Model 是一个接口,ModelMap 是一个接口实现,作用是将model数据填充到request 域。 绑定“ 默认数据” 类型 例子如下 :
第一步、创建项目,导入依赖 :
Spring MVC所需JAR (百度网盘)第二步 :配置 web.xml文件 和 springmvc-config.xml 文件 :
web.xml文件 :
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><!-- 配置“前端控制器” --><servlet><servlet-name>dispatcherServlet</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- 配置Springmvc-config.xml中的存放位置 --><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><!-- 配置DispatcherServlet的“映射” --><servlet-mapping><servlet-name>dispatcherServlet</servlet-name><!-- 将所有请求进行“拦截” --><url-pattern>/</url-pattern></servlet-mapping> </web-app>
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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!-- 定义”组件扫描器“,进行根包扫描,让”注解“生效 --><context:component-scan base-package="com.myh.controller"/><!-- 配置“视图解析器” : 让return时只填“视图名”即可,不用填“全名” --><bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><!-- 设置“前缀” --><property name="prefix" value="WEB-INF/jsp/"/><!-- 设置“前缀” --><property name="suffix" value=".jsp"/></bean> </beans>
第三步、创建 UserController.java 和 success.jsp (视图) :
UserController.java :
package com.myh.controller;import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;import javax.servlet.http.HttpServletRequest;/*** @author 莫月辉* @desctiption* @since 2024/3/11 9:55*/ //使用注解定义了一个“控制器”类 @Controller //将该类变成“控制器类”,替代“实现Controller接口的情况”。 public class UserController {//设置前端请求访问的“路径”@RequestMapping("/selectUser")public String selectUser(HttpServletRequest request) {//获得前端存储在HttpServletRequest中的数据/参数String id = request.getParameter("id");System.out.println("前端要传递给后端的信息 : id = "+id);//前端的"url中"是会携带一个"名称为id"的参数的//通过“视图解析器”来寻找到指定的视图,且返回给前端return "success"; //通过String返回值类型,返回一个视图给前端}}
在上面的UserController.java的代码中,使用注解方式定义了一个控制器类,同时定义了 方法的访问路径。在方法参数中使用了 HttpServletRequest类型,并通过该对象的getParameter( )方法获取了指定的参数 ( 简而言之 :前端存储参数在url中,后端通过 HttpServletRequest 对象来获得前端要传递的参数 / 数据 )。后端做出的响应是 : 返回一个视图给前端 。SpringMVC会通过视图解析器在“/WEB-INF/jsp/”路径下寻找success.jsp 文件。
success.jsp :
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head><title>结果页面</title> </head> <body>OK</body> </html>
第四步、 配置Tomcat且运行后,前端url中输入 http://localhost:8080/selectUser?id=1 来访问后端,后端做出响应。
从上图可以看出,后台方法已从请求(url) 中正确地获取到了id的参数信息,这说明使用默认
的HttpServletRequest参数类型已经完成了数据绑定 。
(即前端存储数据在url中,后端通过HttpServletRequest 参数类型来获取“前端传递”的“参数”的过程)。
绑定 “简单数据类型” 类型 (绑定Java“基本数据类型”)
“简单数据类型”的绑定,就是指 Java中 几种基本数据类型 的绑定,如int、String、Double等类型。这里仍然 以绑定 “默认数据” 类型的例子为基础, 进行修改部分代码即可。
绑定“简单数据类型” 类型 例子如下 : (以绑定 “默认数据” 类型的例子为基础,进行部分代码的修改 :)第一步、
修改控制器类,将控制器类UserController中的selectUser( )方法的参数修改为使用简单数据类型 (Java中的“基本数据类型”)的形式 ://设置前端请求访问的“路径”@RequestMapping("/selectUser")public String selectUser(Integer id) { //处理器方法中设置的参数类型为 : 简单数据类型 (Java中的基本数据类型)System.out.println("id = "+id);return "success"; //通过String返回值类型,返回一个视图给前端}
与默认数据类型案例中的selectUser()方法相比,此方法中只是将HttpServletRequest 参数类型替换为了 Integer 类型。
启动项目,并在浏览器中访问地址http://localhost:8080/selectUser?id=1 ,此时可以发现浏览器正确跳转了success.jsp页面 :
从上面的“运行结果”可以看出,使用 ”简单数据类型“ 同样完成了数据绑定,但是用该类型进行”数据绑定“有限制要求 : 前端传递的”参数名“ 和 后端与之进行数据绑定的”参数名“ 要保持一致(即 前后端进行”数据绑定“的参数名要保持一致。),如果参数名不保持一致,会绑定”数据绑定“失败,那 如果想要参数名不一致呢,怎么解决这个问题?
为此,SpringMVC 提供了 @RequestParam( ) 注解来进行 “间接数据绑定” (也就解决了上述的问题)。
@RequestParam注解主要用于对请求中的参数进行定义,在使用时可以 指定它的4个属性 。
属性 说明 value name 属性的别名,这里指 参数的名字,即 入参的请求参数名字 ( 即 前端传来的参数的名字),如 :value=user_id” :表示 请求的参数中名字为 user_id 的参数的值将传入 (给后端)。如果只使用vaule 属性,则可以省略value属性名。 name 指定 请求头绑定的名称。 required 用于 指定参数是否必须,默认是true,表示请求中一定要有相应的参数。 defaultValue 默认值,表示如果请求中没有同名参数时的默认值。 @RequestParam注解 的使用很简单 ,假设浏览器中的请求地址为http://localhost:8080/selectUser?user_id=1,( 此时前后端对应的“参数名”没有保持一致 )那么在后台 selectUser( ) 方法中的使用方式如下 :
@RequestMapping("/selectUser")//此时前后端“参数名”没有保持一致,无法进行“数据绑定”。可用@RequestParam()注解解决这个问题public String selectUser(@RequestParam(value = "user_id") Integer id) {System.out.println("id = "+id);return "success"; //通过String返回值类型,返回一个视图给前端}
上述代码会将请求中user_id 参数的值1赋给方法中的id形参( 属于“前后端参数名”不一致的情况,可用 @RequestParam( )注解解决这个问题 )这样通过输出语句就可以输出 id 形参中的值。
绑定 “POJO类型”
在使用“简单数据类型绑定”时,可以很容易地根据具体需求来定义方法中的形参类型和个数,然而在 实际应用 中,客户端请求可能会传递 多个不同类型的参数数据,如果还使用简单数据类型进行绑定,那么就需要手动编写多个不同类型的参数,这种操作显然比较烦琐。此时就可以使用 POJO类型 ( 普通Java对象类型 ) / (普通Java对象) 解决这个问题。
POJO 类型的数据绑定就是将所有关联的请求参数封装在一个POJO中,然后在方法 ( 处理方法 )中直接使用该 POJO 作为形参来完成数据绑定。
绑定“ POJO类型” 类型 例子如下 :(是在之前的例子代码的前提下,进行代码的部分修改) :
User.java :
package com.myh.po;public class User { private Integer id; private String username; private Integer 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 Integer getPassword() {return password; }public void setPassword(Integer password) {this.password = password; } }
在 控制器UserController类 中,编写接收用户 注册信息 和 向 注册页面跳转 的方法,代码如下 :
UserController类 :
/*** 向“用户注册”页面跳转*/ @RequestMapping("/toRegister") public String toRegister() {return "register"; }/*** 接收用户注册信息*/ @RequestMapping("/registerUser" ) public String registerUser(User user) {//此处的参数为: POJO类型 (SpringMVC的“数据绑定”)String username = user.getUsername();Integer password = user.getPassword();System.out.println("username="+username);System.out.println("password"+password);//返回一个视图给前端return "success"; }
在 /WEB-INF/jsp目录下,创建一个用户 注册页面register.jsp,在该界面中编写用户注册表单,表单需要以POST 方式提交,并且在提交时会发送一条以“/registerUser”结尾的请求消息:
register.jsp :
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>注册页面</title> </head> <body> <%-- ${pageContext.request.contextPath}/registerUser : 获取web应用的上下文路径,并附加/registerUser路径,从而生成完整的URL--%> <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>
ps :
在使用POJO 类型数据绑定时,前端请求的参数名( 本例中指form表单内各元素的name属性值)
必须与要绑定的POJO类中的属性名一样,这样才会自动将请求数据绑定到POJO对象中,否则后台接
收的参数值为 null。
运行项目,访问http://localhost:8080/toRegister ,会跳转到“用户注册页面”。
从上图可以看出,使用 POJO 类型同样可以获取前端请求传递过来的数据信息,这就是
POJO 类型的数据绑定,但此时存在“参数的中文乱码问题”。
解决 请求参数中的中文乱码问题 :
在前端请求中,难免会有中文信息传递,例如上述例子中的“用户名” 和 “密码输入框”中输入用户名“小雪”和密码“123”时,虽然浏览器可以正确跳转到结果页面,但是在控制台中输出的中文信息却出现了乱码。
为了防止前端传入的中文数据出现乱码问题,我们可以使用 Spring 提供的 编码过滤器来统一编码。要使用编码过滤器,只需要在web.xml 中添加如下代码 :
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"version="4.0"><!-- 配置编码过滤器 --><!-- 放置请求参数的“中文乱码问题” --><filter><filter-name>characterEncodingFilter</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>characterEncodingFilter</filter-name><!-- 表示:拦截前端页面中的“所有请求” --><url-pattern>/*</url-pattern></filter-mapping></web-app>
上述代码中,通过 <filter-mapping>元素的配置会拦截前端页面中的所有请求,并交由名称为CharacterEncodingFilter的编码过滤器类进行处理。在 <filter>元素 中,首先配置了编码过滤
器类 org.springframework.web.filter.CharacterEncodingFilter,然后通过 初始化参数设置统一的编
码为 UTF-8。这样所有的请求信息内容都会以UTF-8 的编码格式进行解析。配置完成后就解决了前端请求中携带的参数的“中文乱码”问题。
绑定 “包装 POJO”
使用 简单POJO类型已经可以完成多数的数据绑定 ,但有时客户端请求中传递的参数会比较复杂。例如,在用户查询订单时,页面传递的参数可能包括订单编号、用户名称等信息,这就包含了订单和用户两个对象的信息。如果将订单和用户的所有查询条件都封装在一个简单 POJO中,显然会比较混乱,这时就可以考虑使用 包装POJO 类型 的 数据绑定。
所谓的 包装POJO,就是在 一个POJO 中包含另一个简单POJO ( 一个POJO中有另一个POJO类型的属性)。
例子如,在订单对象中包含用户对象 (一个POJO对象中包含另一个POJO对象类型的属性),这样在使用时,就可以通过 订单查询到用户信息。
(该例子是在 :绑定 “默认数据” 类型的例子的基础上进行修改部分代码的 :)Orders.java
package com.myh.po;/*** 该类中除了常规的属性外,还封装了User类型的属性参数*/ public class Orders {//订单类Orders,该类用于封装“订单”和“用户信息”private Integer OrdersId; //订单编号private User user; //用户POJO,所属用户 (属于一个POJO对象中有另一个POJO对象类型的属性)public Integer getOrdersId() {return OrdersId;}public void setOrdersId(Integer ordersId) {OrdersId = ordersId;}public User getUser() {return user;}public void setUser(User user) {this.user = user;} }
在上述包装 POJO 类中,定义了订单号和 ( 用户POJO对象类型 )的属性及其对应的getter/setter方法。这样订单类中就不仅可以封装订单的基本属性参数,还可以封装User类型的属性参数。
OrdersController.java :
package com.myh.controller;import com.myh.po.Orders; import com.myh.po.User; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;@Controller public class OrdersController { //订单控制器类 : 该类的属性中有一个POJO类型的属性/*** 向“订单查询页面”跳转*/@RequestMapping("/tofindOrderWithUser")public String toFindOrdersWithUser() {return "orders"; //返回一个“视图” - 有关“订单信息”的“视图”}/*** 查询"订单"和"用户信息"*/@RequestMapping("/findOrderWithUser")public String findOrderWithUser(Orders orders) {//获取订单idInteger ordersId = orders.getOrdersId();//获取该订单对应的“客户信息”User user = orders.getUser();String username = user.getUsername();//输出“订单号”System.out.println(ordersId);//输出"用户姓名"System.out.println(username);return "success"; //返回一个视图} }
在上面的 订单控制器类 : OrdersController,在该类中编写一个跳转到订单查询页面的方法和一个查询订单及用户信息的方法。
order.jsp :
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head><title>订单查询</title> </head> <body> <form action="${pageContext.request.contextPath}/findOrderWithUser" method="post">订单查询: <input type="text" name="ordersIs"><br/><%-- 传递的参数是: 所属用户的“用户名”,传递的参数名是 : user.username --%>所属用户: <input type="text" name="user.username"><input type="submit" value="查询"> </form> </body> </html>
注意点 :
在使用 包装POJO类型数据绑定 时,前端请求的参数名编写必须符合以下两种情况。
①如果 查询条件参数 是 包装类 的 直接基本属性,则参数名直接用对应的属性名,如上面代码中的ordersId。
② 如果 查询条件参数 是 包装类 中 “POJO对象类型的” 属性,则 参数名 必须为【 对象名.属性 ],其中[对象名]
要和包装 POJO 中的 对象属性名称一致,【属性]要和包装POJO 中的 对象子属性名一致,如上述代码中
的user.username。
启动项tomcat后,访问地址为 : http://localhost:8080/tofindOrderWithUser ,会跳转到 “订单查询页面” ,收集信息后,点击“查询”后访问后端进行查询"订单"和"用户信息",最后后端响应一个“视图”。可以看出包装POJO类同样完成了数据绑定。
“自定义数据” 绑定 :
- 在一般情况下,使用 基本数据类型 和 POJO类型的参数数据 已经能够满足需求,然而有些 特殊类型的参数是无法在后台进行直接转换 的。
- 例如 “日期数据”就需要开发者 ① 自定义转换器 (Converter ) / ② 格式化( Formatter ) 来进行数据绑定。
Converter (自定义转换器)
Spring 框架提供了一个 Converter (转换器) 用于将 “一种类型的对象” 转换为“另一种类型的对象”。例如,
用户输入的日期形式 可能是 “2017-04-08” 或 “2017/04/08” 的 字符串 ,而要 Spring 将输入的日期 与 后台的Date 进行绑定,则需要将字符串转换为日期 (前端传来的参数是 String类型的,后端的参数类型是Date,此时就要自定义一个“转换器”,来进行数据的“转换”),此时就可以自定义一个Converter类来进行日期转换。自定义Converter类 (自定义“转换器”)需要 实现org.springframework.core.convert.converter.Converter接口。该接口的代码如下 :
public interface Converter<S, T> {T convert(S var1); }
在上述接口代码中,泛型中的 S表示源类型 (如 :前端传递的“参数的类型”),T表示目标类型 (如 : 后端用于“接收数据的参数的类型” ),而 convert(S source) 表示 接口中的方法。
自定义转换器 (Converter ) 例子如下 :
在绑定“默认数据”类型的代码的基础上,进行添加和修改代码,来完成该例子 :
DateConverter.java :
package com.myh.convert;import org.springframework.core.convert.converter.Converter;import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date;/**自定义“日期转换器”(要在springmvc-config.xml中 ① 自定义“类型转换器”的配置 : 配置ConversionServiceFactoryBean ②显示装配了的“自定义类型转换器”)*/ public class DateConverter implements Converter<String, Date> { //自定义转换器,为前端的“String类型”转换为后端需要的“Date类型”数据服务//定义“日期格式”private String datePattern = "yyyy-MM-dd HH:mm:ss";@Overridepublic Date convert(String source) { //参数为: String类型,返回值为Date类型//格式化日期SimpleDateFormat sdf = new SimpleDateFormat(datePattern);try {Date date = sdf.parse(source); //返回一个Date类型的数据return date;} catch (ParseException e) {throw new IllegalArgumentException("无效的的日期格式,请使用这种格式: "+datePattern);}}}
在上述代码中,DateConverter类实现了Converter接口,该接口中 第一个类型String 表示需要被转换的数据类型,第二个类型 Date 表示需要转换成的目标类型。为了让 Spring MVC 知道并使用这个转换器类,还需要在springmvc-config.xml配置文件中编写一个id为conversionService 的 Bean.
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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/mvchttp://www.springframework.org/schema/mvc/spring-mvc.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!-- 定义”组件扫描器“,进行根包扫描,让”注解“生效 --><context:component-scan base-package="com.myh.controller"/><!-- 配置“视图解析器” : 让return时只填“视图名”即可,不用填“全名” --><bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"><!-- 设置“前缀” --><property name="prefix" value="WEB-INF/jsp/"/><!-- 设置“前缀” --><property name="suffix" value=".jsp"/></bean><!-- 自定义"类型转换器"配置 --><bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"><property name="converters"><set><bean class="com.myh.convert.DateConverter"/></set></property></bean><!-- 显示的装配的 “自定义类型转换器” --><mvc:annotation-driven conversion-service="conversionService"/> </beans>
DateController.java :
package com.myh.controller;import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;import java.util.Date;/*** 日期控制器类*/ @Controller //将该类标记为“控制器类” public class DateController {/*** 使用自定义类型数据绑定日期数据*/@RequestMapping("/customDate")public String CustomDate(Date date) {System.out.println("date="+date);return "success"; //返回一个视图}}
此时启动项目,在浏览器地址中访问 :localhost:8080/customDate?date=2024-03-11 23:30:30 :