目录
数据绑定:
1.数据绑定概念
将HTTP请求中的参数一定的方式转换并绑定到控制器类的方法参数中,这个过程就是Spring MVC中的数据绑定。
在数据绑定过程中,Spring MVC框架会通过数据绑定组件(DataBinder)将请求参数串的内容进行类型转换,然后将转换后的值赋给控制器类中方法的形参,这样后台方法就可以正确绑定并获取客户端请求携带的参数。
2.数据绑定的原理:
前后台交互遇到的3个问题:
1…数据绑定前的数据类型转换:前端页面请求带来的参数默认都是字符串,而我们后台需要的java对象有各种类型数据,所以要涉及到各种数据类型的转换,比如前台的年龄(String类型)需要在后台转换成int类型的年龄。当此类参数特别多的时候springmvc怎么帮我们进行转化呢?
2.数据绑定前的数据格式化: 前台表单我们经常会输入 日期和数字,但是系统默认的格式有时候不符合我们的要求,例如我们希望日期是(2019-11-15)格式输入的。这时候就可以用到 数据格式化的内容了。
3.数据合法性校验:虽然我们有前端的数据校验,但是前端字段的校验可以被越过,而后台的校验对于重要数据是必须的。
浏览器这样做可以禁用前台的js脚本,以此来越过前台的校验。
对象封装的过程:
第一步:想创建一个java对象,各项属性都是空的默认值。
第二步:将请求中所有与java对象对应的属性一一设置进来。(相同的属性名才设置)
数据绑定的源码:
getMethodArgumentValues()方法获取调用的处理器方法的参数,通过参数解析器argumentResolvers逐一解析方法的参数,ModelAttributeMethodProcessor类的resolveArgument()方法部分源码如下:
底层代码this.bindRequestParameters(binder, webRequest);代码主要负责数据的绑定工作。
断点调试的时候我们在实体类的setXXX方法上打一个断点。明显看出来setter方法被调用了。
3.数据绑定流程:
数据绑定流程原理★
① Spring MVC 主框架将 ServletRequest 对象及目标方法的入参实例传递给 WebDataBinderFactory 实例,以创建 DataBinder 实例对象
② DataBinder 调用装配在 Spring MVC 上下文中的 ConversionService 组件进行数据类型转换、数据格式化工作。将 Servlet 中的请求信息填充到入参对象中
③ 调用 Validator 组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果 BindingData 对象
④ Spring MVC 抽取 BindingResult 中的入参对象和校验错误对象,将它们赋给处理方法的响应入参
Spring MVC 通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是 DataBinder,运行机制如下:
4.自定义数据绑定的转换器:
将下图所示的表单信息提交并绑定到一个实体类对象中。(多个属性参数写成了一个字符串,中间用-分割。)
1.Converter:
spring框架提供了一个Converter用于将一种类型的对象转换为另一种类型的对象。
而Converter是一个接口,我们自定义的Converter需要实现Converter接口。
转换器要实现的接口:
实现自定义数据转换器的第一步:
自定义类实现Converter接口(org.springframework.core.convert.converter.Converter):
代码如下:
package com.fan.converter;
import com.fan.pojo.User;
import org.springframework.core.convert.converter.Converter;
//Converter<String,User>:将String转为User对象,泛型需要指明
public class MyStringToUserConverter implements Converter<String,User> {
@Override//参数是字符串,返回值是转成的对象
public User convert(String source) {
System.out.println("这是我的自定义转换器在工作-------");
System.out.println("页面提交的需要转换的字符串:"+source);
User user = new User();
if(source.contains("-")){
//分割字符串
String[] split = source.split("-");
//给对象赋值,即给创建的对象赋前端的参数值
user.setName(split[0]);
user.setAge(Integer.parseInt(split[1]));
}
return user;
}
}
在convert方法中编写自定义的转换规则:
上面的代码中,Converter<String,User>:将String转为User对象,泛型需要指明。该接口中的第一个类型String表示需要被转换的数据类型,第二个类型User表示需要转成的目标类型。第二个类型也可以是其他类型,如Date等;
自定义数据转换器的第二步:
显示的配置自定义类型转换器:
代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
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/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--组件扫描-->
<context:component-scan base-package="com.fan"></context:component-scan>
<!--mvc驱动注解,显示的装配自定义类型转换器-->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
<!--自定义类型转换器配置-->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.fan.converter.MyStringToUserConverter"></bean>
</set>
</property>
</bean>
<!--视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
</beans>
总结:自定义类型的转换器,三步曲:
自定义数据转换器的第三步:
index.jsp中的表单
<%--
Created by IntelliJ IDEA.
User: fan
Date: 2021/3/25
Time: 9:20
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/quickaddUser" method="post">
<%--将用户的所有信息都写上,自动封装对象--%>
<input type="text" name="userinfo" value="admin-30">
<input type="submit" value="快速添加">
</form>
</body>
</html>
controller中的代码测试:
package com.fan.controller;
import com.fan.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class UserConverterController {
@RequestMapping("/quickaddUser")
public String quickAdd(@RequestParam("userinfo")User user){
System.out.println("封装:"+user);
System.out.println(user.getClass());
return "success";
}
}
mvc:annotation-driven的作用:
系统中有一个接口下面有好多专门解析各种标签的类:
在解析这个mvc注解驱动的时候,系统帮我们添加了好多东西。
两个注解都加上,则静态资源和动态资源都能正常运行:
数据格式化:
1.对属性对象输入输出的数据进行格式化,本质上还是属于数据类型的转换。
2.Spring 在格式化模块中定义了一个实现 ConversionService 接口的 FormattingConversionService 实现类,该实现类扩展了 GenericConversionService,因此它既具有类型转换的功能,又具有格式化的功能。
3.支持对数值类型的属性使用 @NumberFormat 注解,对日期属性@DateTimeFormat注解
4.mvc:annotation-driven/ 默认创建的 ConversionService 实例即为
DefaultFormattingConversionService,也就是说,只要在配置文件中注册了 mvc:annotation-driven/就可以在SpringMVC入参绑定和输出时使用注解驱动。
之前在java类里面我们对于日期的格式化都是使用SimpleDateFormat 来格式化数据
用法:要使用数据的格式化,即规定页面提交的日期格式或者数字格式。比如按照2020-12-18这种格式的日期可以被解析并数据绑定。就必须要使用两个常用的注解:
@DateTimeFormat(pattern = “yyyy-MM-dd”):规定页面提交的日期格式
@NumberFormat(pattern = “”):规定页面提交的数据格式
要使用这两个注解,先要在web.xml中有annotation-driven注解驱动。
然后要导入一个maven坐标:这里使用第三方的框架hibernate-validator验证,比较全。
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.6.Final</version>
</dependency>
然后就可以使用上面的两个注解。
注意:当使用了注解驱动后,但是实体类中没有@DateTimeFormat(pattern = “yyyy-MM-dd”):规定页面提交的日期格式,此时发送请求,可能会报错。框架默认支持的格式为斜线方式。2001/04/08
当我们在后台需要对前端的参数封装的javabean进行校验的时候,还需要用到一个方法中参数的注解:
public String addUser(@Valid User user){}//表示校验user对象,按照其校验规格来。
自定义格式化转换:
当我们既要用我们自定义的类型转换器,又要用格式化的功能,怎么办?
按照如下操作即可:
只需要改这里:
bean id=“conversionService” class=“org.springframework.format.support.FormattingConversionServiceFactoryBean”>
后台数据校验:
所谓的数据校验,就是检验输入的数据和输出的数据是否为指定的格式,跟数据格式化类似,但是比格式化更加强大。
通过在处理方法的入参上标注 @Valid 注解即可让 Spring MVC 在完成数据绑定后执行数据校验的工作。
JSR 303:
是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 中 . JSR 303 (Java Specification Requests意思是Java 规范提案)通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证
Hibernate Validator 扩展注解:
Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解
如何校验?在SpringMVC中分为以下几步骤:
①使用JSR 303验证标准
②加入hibernate validator验证框架
③在SpringMVC配置文件中增加 mvc:annotation-driven/
④需要在bean的属性上增加对应验证的注解
⑤在目标方法bean类型的前面增加@Valid注解
前端页面:使用springmvc的form表单标签,更方便数据的回显:
在页面上检验错误信息:
将验证错误的信息显示在可以用表单标签,Spring MVC 除了会将表单/命令对象的校验结果保存到对应的 BindingResult 或 Errors 对象中外,还会将所有校验结果保存到 “隐含模型”。
在 JSP 页面上可通过 <form:errors path=“userName”> 显示错误消息。
代码演示:
index.jsp页面:
<%--
Created by IntelliJ IDEA.
User: fan
Date: 2021/3/26
Time: 9:18
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<a href="hello">测试hello</a><br>
<a href="toForm1">利用view-controller去到toForm1</a>
</body>
</html>
springmvcform1.jsp页面(此页面在WEB-INF/pages下):
<%--
Created by IntelliJ IDEA.
User: fan
Date: 2021/3/26
Time: 9:41
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<html>
<head>
<title>这里是springmvc的表单标签</title>
</head>
<body>
<h1>这里是springmvc的表单标签</h1>
<%--使用自带form标签是,必须先通过model对象,传递了一个空的user对象到页面.--%>
<form:form action="${pageContext.request.contextPath}/addUser" modelAttribute="user" method="post">
<%--<form:errors path="birth">用于显示错误消息,从后台的隐含模型中拿错误消息--%>
用户名:<form:input path="name"></form:input><form:errors path="name"></form:errors><br>
年龄:<form:input path="age"></form:input><form:errors path="age"></form:errors><br>
生日:<form:input path="birth"></form:input> <form:errors path="birth"></form:errors><br>
<input type="submit" value="提交">
</form:form>
</body>
</html>
实体类代码(带数据格式化和JSR303校验:)User:
package com.fan.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.NumberFormat;
import javax.validation.constraints.Max;
import javax.validation.constraints.Past;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@Length(min = 3)//最小长度3位
private String name;
@Max(150)//最大年龄150岁
private Integer age;
/*前端请求参数封装的时候要经过这里的set方法*/
@Past//必须是一个过去的时间,比如生日
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birth;
}
Controller代码:
package com.fan.controller;
import com.fan.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.validation.Valid;
@Controller
public class UserController {
@RequestMapping(value = "/hello")
public String hello(){
return "success";
}
@RequestMapping(value = "/toForm1")
public String toForm1(Model model){
model.addAttribute("user",new User());
return "springmvcform1";
}
@RequestMapping("/addUser")
public String addUser(@Valid User user, BindingResult result){
System.out.println("前端封装的数据:"+user);
return "springmvcform1";//必须转发返回到添加页面才能看到错误消息提示;
}
}
验证显示:
原生表单怎么获取错误消息:
国际化定制自己的错误消息显示:
准备国际化文件:
国际化文件的错误消息的key遵循的规则:有以下四种:
第一步:编写国际化文件内容:
第二步:让springmvc管理国际化文件:使用一个类:ResourceBundleMessageSource
springmvc.xml中的配置(让spring管理消息资源):
<!--加载国际化的资源文件-->
<bean class="org.springframework.context.support.ResourceBundleMessageSource">
<!--资源文件的前缀名字-->
<property name="basename" value="com.fan.conf.error"></property>
</bean>
最终效果:
还可以这样进行消息提示,但是此种方式不能国际化: