(五)SpringMVC学习笔记-数据转换 & 数据格式化 & 数据校验
一、数据转换
默认数据转换器
Spring MVC 上下文中内建了很多转换器,可完成大多数 Java 类型的转换工作。
比如我们在页面的文本框上输入了整数或者是布尔值的数据,虽然在文本框的格式依旧是字符串,但是SpringMVC支持将这些数据转成对应的格式。
比如现在有如下的页面代码如下所示:
<form action="testUserConvert" method="POST">
User ID:<input type="text" name="uid"/><br/>
Username:<input type="text" name="username"/><br/>
PassWord:<input type="password" name="password"/><br/>
is Fale:<input type="text" name="isGender"/><br/>
<input type="submit" value="Submit"/>
</form>
我们定义有对应的User类用于保存文本输入的数据,具体的属性如下所示:
private Integer uid;
private String username;
private String password;
private Boolean isGender;
在点击提交之后,会经过如下的控制器方法,SpringMVC会自动将文本框输入的数据以对应的数据类型封装在User类中。
@RequestMapping("/testUserConvert")
public String testUserConvert(User user) {
System.out.println(user);
return "success";
}
也就是SpringMVC会将文本框中的字符串uid值和字符串isGender值转为User类对应的数据类型。这个过程即为数据转换。
SpringMVC默认支持如下的数据转换功能:
自定义数据转换器
比如我们在页面文本框上输入"100-shoto-abc123-true",并希望其转换为对应的User对象,那么这时就可以让自定义数据转换器出场了。
- 首先需要在页面编写如下语句:
<!-- 需求:将这种格式的字符串数据"100-shoto-abc123-true"转为对应的User类 -->
<form action="testConversionServiceConverter" method="POST">
<input type="text" name="user"/>
<input type="submit" value="Submit"/>
</form>
- 在控制器类编写如下的方法,其可以接收经过自定义数据转换器转换过的user对象,并打印出来:
@RequestMapping(value="/testConversionServiceConverter",method=RequestMethod.POST)
public String testUserConverter(@RequestParam("user") User user) {
System.out.println(user);
return "success";
}
- 接下来就是要编写自定义的数据转换类了,Spring 定义了 3 种类型的转换器接口,如下示:
我们现在就仅仅演示一下Converters接口的使用,定义UserConverter类,具体如下所示:
//Converter<String,User>表示将String类型转为User类型的对象
public class UserConverter implements Converter<String,User>{
//source为传递进来的String值,如100-shoto-abc123-true
@Override
public User convert(String source) {
if (source != null) {
String[] userStr = source.split("-");
Integer uid = Integer.parseInt(userStr[0]);
String username = userStr[1];
String password = userStr[2];
Boolean isGender = Boolean.parseBoolean(userStr[3]);
return new User(uid, username, password, isGender);
}
return null;
}
}
- 在编写自定义的转换器类后,我们需要在MVC配置文件进行配置。可以利用 ConversionServiceFactoryBean 在 Spring 的 IOC容器中定义一个 ConversionService. ConversionService 是 Spring 类型转换体系的核心接口。Spring 将自动识别出IOC 容器中的 ConversionService,并在 Bean 属性配置及Spring MVC 处理方法入参绑定等场合使用它进行数据的转换。可通过ConversionServiceFactoryBean 的 converters 属性注册自定义的类型转换器,具体如下示:
<!-- 配置自定义数据类型转换器 -->
<bean id="conversionServiceFactoryBean" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean id="userConverter" class="com.shoto.springmvc.converters.UserConverter"/>
</list>
</property>
</bean>
- 最后一步,需要配置annotation-driven,具体如下示:
<!-- 使用annotation-driven将conversionService注册到SpringMVC上下文中 -->
<mvc:annotation-driven conversion-service="conversionServiceFactoryBean"/>
其支持使用 ConversionService 实例对表单参数进行类型转换。
使用@InitBinder
由 @InitBinder 标识的方法,可以对 WebDataBinder 对象进行初始化。WebDataBinder是 DataBinder 的子类,用于完成由表单字段到 JavaBean 属性的绑定。
@InitBinder方法不能有返回值,它必须声明为void。@InitBinder方法的参数通常是 WebDataBinder。
基于上述默认数据转换器的例子,演示一下@InitBinder注解的使用。
假如现在我不需要实现表单中的password字段到User对象的password属性的绑定。那么可以进行如下操作。
首先需要在控制器类中添加如下方法:
/**
* 不自动绑定User对象的password属性
*/
@InitBinder
public void initBinder(WebDataBinder dataBinder) {
dataBinder.setDisallowedFields("password");
}
然后进行请求响应之后,testUserConvert的打印结果如下所示:
User [uid=100, username=abc, password=null, isGender=true]
也就是没有实现表单中的password字段到User对象的password属性的绑定,此时password属性值为null。
二、数据格式化
假如我们在表单上输入指定格式的日期或者数值,这是就需要对输入的值进行数据格式化处理。对属性对象的输入/输出进行格式化,从其本质上讲依然属于 “类型转换” 的范畴。
Spring 在格式化模块中定义了一个实现ConversionService 接口的FormattingConversionService 实现类,该实现类扩展了 GenericConversionService,因此它既具有类型转换的功能,又具有格式化的功能。FormattingConversionService 拥有一个FormattingConversionServiceFactroyBean 工厂类,后者用于在 Spring 上下文中构造前者。
FormattingConversionServiceFactroyBean 内部已经注册了如下两个类:
- NumberFormatAnnotationFormatterFactroy:支持对数字类型的属性使用 @NumberFormat 注解
- JodaDateTimeFormatAnnotationFormatterFactroy:支持对日期类型的属性使用 @DateTimeFormat 注解。
装配了 FormattingConversionServiceFactroyBean 后,就可以在 Spring MVC 入参绑定及模型数据输出时使用注解驱动了。<mvc:annotation-driven/> 默认创建的ConversionService 实例即为FormattingConversionServiceFactroyBean。
数值格式化
@NumberFormat 可对类似数字类型的属性进行标注,它拥有两个互斥的属性:
- style:类型为 NumberFormat.Style。用于指定样式类型,包括三种Style.NUMBER(正常数字类型)、Style.CURRENCY(货币类型)、Style.PERCENT(百分数类型)
- pattern:类型为 String,自定义样式,如patter="#,###";
日期格式化
@DateTimeFormat 注解可对java.util.Date、java.util.Calendar、java.long.Long 时间
类型进行标注:
-
pattern 属性:类型为字符串。指定解析/格式化字段数据的模式,如:”yyyy-MM-dd hh:mm:ss”
-
iso 属性:类型为 DateTimeFormat.ISO。指定解析/格式化字段数据的ISO模式,包括四种:ISO.NONE(不使用-默认)、ISO.DATE(yyyy-MM-dd) ISO.TIME(hh:mm:ss.SSSZ)、ISO.DATE_TIME(yyyy-MM-dd hh:mm:ss.SSSZ)
-
style 属性:字符串类型。通过样式指定日期时间的格式,由两位字符组成,第一位表示日期的格式,第二位表示时间的格式:
S:短日期/时间格式、M:中日期/时间格式、L:长日期/时间格式、F:完整日期/时间格式、-:忽略日期或时间格式
示例:
- 首先需要在页面编写如下代码,即表单代码:
<form action="formatDateAndNumber" method="POST">
User ID:<input type="text" name="uid"/><br/>
Username:<input type="text" name="username"/><br/>
PassWord:<input type="password" name="password"/><br/>
is Fale:<input type="text" name="isGender"/><br/>
salary:<input type="text" name="salary"/><br/>
birthday:<input type="text" name="birthday"/><br/>
<input type="submit" value="Submit"/>
</form>
- 编写对应的User类,其属性如下所示:
private Integer uid;
private String username;
private String password;
private Boolean isGender;
@NumberFormat(pattern="###,###,###.##")
private Double salary;
@DateTimeFormat(iso = ISO.DATE)//格式是yyyy-MM-dd
private Date birthday;
注意:这里使用到了格式化用的注解。
- 接着需要在控制器类上定义如下方法:
@RequestMapping(value="/formatDateAndNumber",method=RequestMethod.POST)
public String formatDateAndNumber(User user) {
//输出:User [uid=100, username=abc, password=abc123, isGender=true,
//salary=100452.12, birthday=Thu Feb 14 08:00:00 CST 2019]
System.out.println(user);
return "success";
}
- 最后,需要在MVC配置文件中进行如下配置:
<mvc:annotation-driven></mvc:annotation-driven>
<mvc:annotation-driven/> 默认创建的ConversionService 实例即为FormattingConversionServiceFactroyBean,后者可以在 Spring MVC 入参绑定及模型数据输出时使用注解驱动,以完成@NumberFormat annotation、@DateTimeFormat注解数据类型的格式化。
三、数据校验
JSR 303
JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 中 。
JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max等标准的注解指定校验规则,并通过标准的验证接口对 Bean进行验证。下面显示的是一些标准的校验注解:
Hibernate Validator 扩展注解
Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解:
SpringMVC 数据校验
我们下面演示一下Hibernate Validator框架进行数据校验的使用:
Spring 4.0 拥有自己独立的数据校验框架,同时支持 JSR 303 标准的校验框架。因为Spring 本身并没有提供 JSR303 的实现,所以必须将JSR303 的实现者的 jar 包放到类路径下。那么现在首先需要导入Hibernate Validator所需的jar包:
假如定义有如下的表单,具体的代码如下所示:
<form action="format" method="POST">
User ID:<input type="text" name="uid"/><br/>
Username:<input type="text" name="username"/><br/>
PassWord:<input type="password" name="password"/><br/>
is Fale:<input type="text" name="isGender"/><br/>
salary:<input type="text" name="salary"/><br/>
birthday:<input type="text" name="birthday"/><br/>
Email:<input type="text" name="email"/><br/>
<input type="submit" value="Submit"/>
</form>
Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作。在 Spring MVC 中,可直接通过注解驱动的方式进行数据校验。那么现在需要对表单所对应的User对象配置相关的校验注解,具体如下所示:
Spring 的 LocalValidatorFactroyBean 既实现了 Spring 的Validator 接口,也实现了 JSR 303 的 Validator 接口。只要在 Spring 容器中定义了一个LocalValidatorFactoryBean,即可将其注入到需要数据校验的 Bean 中。因为 <mvc:annotation-driven/> 会默认装配好一个LocalValidatorFactoryBean 。那么下面即需要在MVC配置文件中配置<mvc:annotation-driven/>。
接着需要在控制器类定义如下目标方法,具体代码如下所示:
@RequestMapping(value="/format",method=RequestMethod.POST)
public String formatDateAndNumber(@Valid User user, BindingResult result) {
System.out.println(user);
if (result.getErrorCount() > 0) {
System.out.println(result.getFieldError());
return "redirect:/index.jsp";
}
return "success";
}
注解@Valid的使用,可以让Spring MVC 框架在将请求参数绑定到该入参对象后,就会调用校验框架根据注解声明的校验规则实施校验。
入参BindingResult的作用是将前一个表单/命令对象(如User)的校验结果保存到随后的入参中, 也就是保存到BindingResult对象中。这个保存校验结果的入参必须是 BindingResult 或Errors 类型,这两个类都位于org.springframework.validation 包中。另外,需校验的 Bean 对象和其绑定结果对象或错误对象时是成对出现的,它们之间不允许声明其他的入参。也就是需要保持如下的格式:
页面显示校验结果
Spring MVC 除了会将表单/命令对象的校验结果保存到对应的 BindingResult 或 Errors 对象中外,还会将所有校验结果保存到 “隐含模型”。即使处理方法的签名中没有对应于表单/命令对象的结果入参,校验结果也会保存在 “隐含对象” 中。
隐含模型中的所有数据最终将通过 HttpServletRequest 的属性列表暴露给 JSP 视图对象,因此在 JSP 中可以获取错误信息。在 JSP 页面上可通过 <form:errors path=“userName”>显示错误消息。
示例:
假如现在在列表页面点击添加员工的超链接,并会经过如下的控制器类的目标方法进行处理:
@RequestMapping(value="/emp",method=RequestMethod.GET)
public String input(Map<String, Object> map) {
//键"employee"匹配input.jsp页面的form表单的modelAttribute的属性值
map.put("employee", new Employee());
//需要将所有部门信息放在请求域中,在员工信息添加页面以下拉列表的形式显示部门名称
map.put("departments", departmentDao.getDepartments());
return "input";
}
执行后跳转到员工信息的输入页面input.jsp,其中的表单代码如下所示:
<!--
可以通过 modelAttribute 属性指定绑定的模型属性,若没有指定该属性,
则默认从 request 域对象中读取command 的表单 bean,
如果该属性值也不存在,则会发生错误。
-->
<form:form action="${pageContext.request.contextPath }/emp" method="POST" modelAttribute="employee">
<!-- path:表单字段,对应 html 元素的 name 属性,支持级联属性
对应指定类的属性名
-->
<!-- id不为空时不显示LastName,也就是LastName不可修改
当添加员工信息时,也就是员工id为空时会显示LastName
-->
<c:if test="${employee.id == null}">
LastName: <form:input path="lastName"/><br/>
</c:if>
<c:if test="${employee.id != null}">
<form:hidden path="id"/>
<input type="hidden" name="_method" value="PUT"/>
</c:if>
Email: <form:input path="email"/>
<br/>
<!--
form:radiobuttons:单选框组标签,用于构造多个单选框.
items:可以是一个 List、String[] 或 Map
-->
<%
//注意:new HashMap<>();这种格式JDK1.7以上才支持,否则JSP编译出错
Map<String,String> genders = new HashMap();
genders.put("1", "Male");
genders.put("0", "Female");
//需要放入请求域中,否则EL表达式无法取到值
request.setAttribute("genders", genders);
%>
<!-- 多个单选框可以通过 delimiter 指定分隔符 -->
Gender: <br/><form:radiobuttons path="gender" items="${genders }" delimiter="<br>"/><br/>
<!-- 下拉列表
itemValue:指定 radio 的 value 值。可以是集合中 bean 的一个属性值
itemLabel:指定 radio 的 label 值
-->
Department: <form:select path="department.id"
items="${departments}" itemLabel="departmentName" itemValue="id"/><br/>
<input type="submit" name="提交"/>
</form:form>
我们在Employee类的email属性使用了@Email校验注解。当我们点击员工信息输入页面的提交按钮后,会经过如下的控制器类的目标方法进行处理:
/**
* 添加一个员工
*/
@RequestMapping(value="/emp",method=RequestMethod.POST)
public String save(@Valid Employee employee, BindingResult result, Map<String, Object> map) {//SpringMVC自动将标签的属性装载进Employee对象中
if (result.getErrorCount() > 0) {
for (FieldError error : result.getFieldErrors()) {
System.out.println(error.getField() + ":" + error.getDefaultMessage());
}
//需要将所有部门信息放在请求域中,在员工信息输入页面以下拉列表的形式显示部门名称
map.put("departments", departmentDao.getDepartments());
/*
* 查看map中的employee是否有值用于回显?为什么map中有入参enployee的值
* 为什么不用map.put("employee", employee);?雾。。。
*/
//System.out.println(map.get("employee"));
//Employee [id=null, lastName=abc, email=abc, gender=1, department=Department [id=101, departmentName=null]]
return "input";//重新跳转到输入页面
}
//校验通过后才能保存员工数据
employeeDao.save(employee);
return "redirect:/emps";//重定向到显示所有员工的list方法
}
最后我们需要在员工信息输入页面添加<form:errors/>,当email输入错误时,会显示在页面上。
提示消息国际化
我们可以实现错误消息的自定义或国际化。当一个属性校验失败后,校验框架会为该属性生成 4 个消息代码,这些代码以校验注解类名为前缀,结合modleAttribute、入参对象名及入参属性名生成多个对应的消息代码:例如 User 类中的 password 属性使用了一个 @Pattern 注解,当该属性值不满足 @Pattern 所定义的规则时, 就会产生以下 4个错误代码:
- Pattern.user.password
- Pattern.password
- Pattern.java.lang.String
- Pattern
当使用 Spring MVC 标签显示错误消息时, Spring MVC 会查看WEB 上下文是否装配了对应的国际化消息,如果没有,则显示默认的错误消息,否则使用国际化消息。
下面演示一下其使用:
首先我们需要创建一个国际化资源文件i18n.properties,如下所示:
接着需要在MVC配置文件进行国际化资源注册,具体如下:
<!-- 注册国际化资源文件 -->
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"></property>
</bean>
请求响应后,若输入的Email不合法,在浏览器上会根据浏览器设置的本地语言对应显示英文或者中文的字样的出错提示信息。
若数据类型转换或数据格式转换时发生错误,或该有的数不存在,或调用处理方法时发生错误,都会在隐含模型中创建错误消息。其错误代码前缀说明如下:
- required:必要的参数不存在。如 @RequiredParam(“param1”)标注了一个入参,但是该参数不存在;
- typeMismatch:在数据绑定时,发生数据类型不匹配的问题;
- methodInvocation:Spring MVC 在调用处理方法时发生了错误;
比如在数据绑定时,表单输入的Birth不是一个日期,也就是发生了数据类型转换的错误,此时可以在i18n.properties进行如下配置:
然后进行国际化资源注册即可。
四、数据绑定流程
Spring MVC 通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中的过程即为数据绑定。绑定的流程如下所示:
- Spring MVC 主框架将 ServletRequest 对象及目标方法的入参实例传递给WebDataBinderFactory 实例,以创建 DataBinder 实例对象;
- DataBinder 调用装配在 Spring MVC 上下文中的ConversionService 组件进行数据类型转换、数据格式化工作。将 Servlet 中的请求信息填充到入参对象中;
- 调用 Validator 组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果BindingData 对象;
- Spring MVC 抽取 BindingResult 中的入参对象和校验错误对象,将它们赋给处理方法的响应入参。
数据绑定的核心部件是DataBinder,运行机制如下:
推荐阅读
-
(五)SpringMVC学习笔记-数据转换 & 数据格式化 & 数据校验
-
springmvc学习----8.数据转换&数据格式化&数据校验
-
SpringMVC - 类型转换 - 数据格式化 -数据校验
-
SpringMVC学习笔记 | 数据转换、数据格式化、数据校验
-
小白Java学习笔记d8(==。equals,数据类型的转换)
-
【JavaScript学习笔记】6:数据类型,object类型及其判定,常用类型转换
-
Struts2学习笔记(6)-简单的数据校验
-
Struts2学习笔记(6)-简单的数据校验
-
Angular4学习笔记(五)- 数据绑定、响应式编程和管道
-
黑马旅游网学习笔记之分类数据展示功能(五)