数据转换
1. 数据绑定流程
SpringMVC通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中。其中,数据绑定的核心部件是DataBinder,运行机制如下
数据绑定的具体流程说明如下
- SpringMVC主框架将ServletRequest对象和目标方法的入参实例传递给WebDataBinderFactory实例,以创建DataBinder实例对象;
- DataBinder调用装配在SpringMVC上下文中的ConversionService组件进行数据类型转换与格式化操作,并将Servlet中的请求信息填充到入参对象中;
- SpringMVC进而调用Validator组件对已经绑定请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果BindingData对象;
- SpringMVC抽取BindingResult中的入参对象和校验错误对象,将其赋给处理方法的响应入参中。
// ModelAttributeMethodProcessor的resolveArgument()方法的核心代码:
WebDataBinder binder = binderFactory.createBinder(request, attribute, name);
if (binder.getTarget() != null) {
bindRequestParameters(binder, request); //绑定请求数据到入参中
validateIfApplicable(binder, parameter); //校验入参中数据的合法性
if (binder.getBindingResult().hasErrors()) {
if (isBindExceptionRequired(binder, parameter)) {
throw new BindException(binder.getBindingResult());
}
}
}
2. 数据类型转换
SpringMVC上下文中内建了许多数据类型转换器,可完成大多数Java类型的换工作,具体可通过调试模式查看binder的conversionService属性值。
3. 自定义数据类型转换器
- 明确需求:
<form action="testConversionServiceConverter" method="POST">
<!-- lastName-email-gender-department.id 例如 : ZZ-zz@qq.com-0-105 -->
Employee : <input type="text" name="employee">
<input type="submit" value="submit">
</form>
@Controller
public class SpringMVCTest {
@Autowired
private EmployeeDao employeeDao;
@RequestMapping("/testConversionServiceConverter")
public String testConverter(@RequestParam("employee") Employee employee) {
employeeDao.save(employee);
return "redirect:emps";
}
}
- 第一步:自定义实现Converter接口的数据类型转换器类,并添加到Spring的IoC容器中;
// 三种类型的数据转换器接口:Converter<S,T>、ConverterFactory、GenericConverter
@Component
public class EmployeeConverter implements Converter<String, Employee>{
public Employee convert(String source) {
if(source != null) {
String[] strs = source.split("-");
if(strs != null && strs.length == 4) {
String name = strs[0];
String email = strs[1];
String gender = strs[2];
Department department = new Department();
department.setDeptId(Integer.parseInt(strs[3]));
return new Employee(null, name, email, gender, department);
}
}
return null;
}
}
- 第二步:在SpringMVC的配置文件中,通过ConversionServiceFactoryBean的converters属性注册自定义的类型转换器;
<!--
配置数据类型转换器:
1). ConversionService是SpringMVC类型转换体系的核心接口;
2). 可以利用ConversionServiceFactoryBean在Spring的IoC容器中定义一个ConversionService;
3). Spring将自动识别IOC容器中的ConversionService,并在Bean属性配置及SpringMVC处理方法入参绑定等场合使用其进行数据类型转换。
-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="employeeConverter"/>
</set>
</property>
</bean>
- 第三步:通过mvc:annotation-driven标签的conversion-service属性,将ConversionServiceFactoryBean注册到SpringMVC的上下文中。
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
4. 关于mvc:annotation-driven
<mvc:annotation-driven/> 会自动注册 RequestMappingHandlerMapping、
RequestMappingHandlerAdapter 与 ExceptionHandlerExceptionResolver
三个bean,其还将提供如下支持:
- 支持使用ConversionService实例对表单参数进行类型转换;
- 支持使用@NumberFormat和@DateTimeFormat注解完成数据类型的格式化;
- 支持使用@Valid注解对JavaBean实例进行JSR 303验证;
- 支持使用@RequestBody和@ResponseBody注解。
5. @InitBinder注解
由@InitBinder注解标识的方法,可以对 WebDataBinder 对象进行初始化;WebDataBinder是DataBinder的子类,用于完成由表单字段到JavaBean属性的绑定。注意@InitBinder方法不能有返回值,其入参通常是WebDataBinder对象。
/**
* 在进行数据绑定时,不自动绑定对象中的name属性
* 注意:其对自定义类型转换器并不起作用
* @param binder
*/
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.setDisallowedFields("name");
}
数据格式化
1. FormattingConversionServiceFactroyBean
主要作用:用于在Spring上下文中构造FormattingConversionService。
其中,FormattingConversionServiceFactroyBean工厂类的内部已经注册:
- NumberFormatAnnotationFormatterFactroy:支持对数字类型的属性使用@NumberFormat注解;
- JodaDateTimeFormatAnnotationFormatterFactroy:支持对日期类型的属性使用@DateTimeFormat注解;
2. mvc:annotation-driven
- mvc:annotation-driven 默认创建的 ConversionService实例即为FormattingConversionServiceFactroyBean;
- 故配置mvc:annotation-driven后即可在SpringMVC入参绑定及模型数据输出时使用注解驱动,以实现数据类型转换和数据格式化
3. FormattingConversionService
- 该类是Spring格式化模块中实现了ConversionService接口的实现类;
- 该类扩展了GenericConversionService,既具有类型转换的功能,又具有格式化的功能。
4. 日期格式化
@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表示完整日期/时间格式、-表示忽略日期或时间格式。
5. 数值格式化
@NumberFormat注解可对类似数字类型的属性进行标注,其具有两个互斥的属性:
- pattern属性:字符串类型,用于自定义样式,如”#,###,###.#”;
- style属性:类型为NumberFormat.Style,用于指定样式类型,包括Style.NUMBER(正常数字类型)、Style.CURRENCY(货币类型)、Style.PERCENT(百分数类型)。
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birth;
@NumberFormat(pattern="###,###,###.##")
private float salary;
数据校验
1 JSR303数据校验
注解 功能说明
@Null 被注释的元素必须为null
@NotNull 被注释的元素必须不为null
@AssertTrue 被注释的元素必须为true
@AssertFalse 被注释的元素必须为false
@Min(value) 被注释的元素必须是一个数字,其值必须大于或等于指定的最小值
@Max(value) 被注释的元素必须是一个数字,其值必须小于或等于指定的最大值
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于或等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于或等于指定的最大值
@Size(max, min) 被注释的元素的大小必须在指定的范围内
@Digits(integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Pattern(value) 被注释的元素必须符合指定的正则表达式
Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,还支持一下扩展注解:
注解 功能说明
@Email 被注释的元素必须是电子邮箱地址
@Length 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串必须非空
@Range 被注释的元素必须在合适的范围内
2 SpringMVC数据校验
2.1 基本概念
① 关于所需要的jar包
Spring4.0拥有独立的数据校验框架,同时支持 JSR 303 标准的校验框架;但其本身并没有提供 JSR 303 的实现,故必须将 JSR 303 的实现者的jar包放到类路径下。
② 关于LocalValidatorFactoryBean工厂类
该工厂类既实现了Spring的Validator接口,也实现了 JSR 303 的Validator接口;故需要在 Spring 容器中定义LocalValidatorFactoryBean,即可将其注入到需要数据校验的Bean中。
③ 关于@Valid注解
会默认装配LocalValidatorFactoryBean,通过在处理方法的入参上标注@Valid注解即可让SpringMVC在进行数据绑定时,同时调用校验框架完成数据校验工作。
④ 关于校验结果
前一个表单/命令对象的校验结果保存到随后处理方法的入参中,该入参必须是BindingResult或Errors类型;且需注意,需校验的Bean对象和其绑定结果对象或错误对象是成对出现的,其之间不允许声明其他的入参。
2.2 具体实现
第一步:添加Hibernate Validator验证框架所依赖的jar包,具体如下图所示:
第二步:在SpringMVC配置文件中添加mvc:annotation-driven标签;
第三步:在需要校验的JavaBean属性上添加相应的校验注解,以Employee为例:
public class Employee {
@NotNull
private String name;
@Email
private String email;
@Past
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birth;
// ……
}
第四步:在处理器目标方法的Bean类型的入参前添加@Valid注解,并添加保存校验结果的对象:
@RequestMapping(value="/emp", method=RequestMethod.POST)
public String save(@Valid Employee employee, BindingResult result, Map<String, Object> map) {
System.out.println(employee);
if(result.getErrorCount() > 0) {
for(FieldError error : result.getFieldErrors()) {
System.out.println(error.getField() + " : " + error.getDefaultMessage());
}
// 指定校验错误时所转向的定制页面
map.put("departments", departmentDao.getDepartments());
return "input";
}
employeeDao.save(employee);
return "redirect:/emps";
}