一、数据绑定流程
- 1. Spring MVC 主框架将 ServletRequest 对象及目标方 法的入参实例传递给 WebDataBinderFactory 实例,以创建 DataBinder 实例对象
- 2. DataBinder 调用装配在 Spring MVC 上下文中的 ConversionService 组件进行数据类型转换、数据格式 化工作。将 Servlet 中的请求信息填充到入参对象中
- 3. 调用 Validator 组件对已经绑定了请求消息的入参对象 进行数据合法性校验,并最终生成数据绑定结果BindingData 对象
- 4. Spring MVC 抽取 BindingResult 中的入参对象和校验 错误对象,将它们赋给处理方法的响应入参
Spring MVC 通过反射机制对目标处理方法进行解析,将请 求消息绑定到处理方法的入参中。数据绑定的核心部件是DataBinder,运行机制如下:
查看代码-数据绑定流程:
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null) { if (!mavContainer.isBindingDisabled(name)) { bindRequestParameters(binder, webRequest); } validateIfApplicable(binder, parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new BindException(binder.getBindingResult()); } }
@InitBinder
- 由 @InitBinder 标识的方法,可以对 WebDataBinder 对 象进行初始化。WebDataBinder 是 DataBinder 的子类,用 于完成由表单字段到 JavaBean 属性的绑定
- @InitBinder方法不能有返回值,它必须声明为void。
- @InitBinder方法的参数通常是是 WebDataBinder
//在控制器中编写的代码 //对内些无法自动绑定的对象,进行特殊处理。比如 表单中的选择器上传Id值,然后这里可以改成name值 @InitBinder public void initBinder(WebDataBinder binder){ //这里代表:将表单中的lastName 不赋值给 bean中的 lastName binder.setDisallowedFields("lastName"); }
二、自定义类型转换器
ConversionService 是 Spring 类型转换体系的核心接口。
可以利用 ConversionServiceFactoryBean 在 Spring 的 IOC容器中定义一个 ConversionService. Spring 将自动识别出IOC 容器中的 ConversionService,并在 Bean 属性配置及Spring MVC 处理方法入参绑定等场合使用它进行数据的转换。
可通过 ConversionServiceFactoryBean 的 converters 属性 注册自定义的类型转换器。
======================================================
- Spring 定义了 3 种类型的转换器接口,实现任意一个转换器接口都可以作为自定义转换器注册到ConversionServiceFactroyBean 中:
–Converter<S,T>:将 S 类型对象转为 T 类型对象
–ConverterFactory:将相同系列多个 “同质” Converter 封装在一 起。如果希望将一种类型的对象转换为另一种类型及其子类的对 象(例如将 String 转换为 Number 及 Number 子类(Integer、Long、Double 等)对象)可使用该转换器工厂类
–GenericConverter:会根据源类对象及目标类对象所在的宿主类中的上下文信息进行类型转换
======================================================
代码参照之前的crud:
测试页面:
<form action="testConversionServiceConverer" method="POST"> <!-- lastname-email-gender-department.id 例如: [email protected] --> Employee: <input type="text" name="employee"/> <input type="submit" value="Submit"/> </form>
控制器处理:
@RequestMapping("/testConversionServiceConverer") public String testConverter(@RequestParam("employee") Employee employee){ System.out.println("save: " + employee); employeeDao.save(employee); return "redirect:/emps"; }
自定义转换器
将表单的 字符串类型 employee 转换成 bean类型 employee;用于表单提交时,传入参数的类型转换。
package com.test.springmvc.test; import org.springframework.core.convert.converter.Converter; import org.springframework.stereotype.Component; import com.test.springmvc.crud.entities.Department; import com.test.springmvc.crud.entities.Employee; @Component public class EmployeeConverter implements Converter<String, Employee> { //自定义转换器必须要 Converter<String, Employee> 然后再去转发配置器中注册 @Override public Employee convert(String source) { if(source != null){ String [] vals = source.split("-"); //[email protected] if(vals != null && vals.length == 4){ String lastName = vals[0]; String email = vals[1]; Integer gender = Integer.parseInt(vals[2]); Department department = new Department(); department.setId(Integer.parseInt(vals[3])); Employee employee = new Employee(null, lastName, email, gender, department); System.out.println(source + "--convert--" + employee); return employee; } } return null; } }
编辑转发配置器
<!-- 配置 ConversionService ,注意下面是 ConversionServiceFactoryBean --> <bean id="conversionService" class="org.springframework.format.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <ref bean="employeeConverter"/> </set> </property> </bean> <mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
三、数据格式化
<mvc:annotation-driven/> 默认创建的 ConversionService 实例即为 FormattingConversionServiceFactroyBean。装配了 FormattingConversionServiceFactroyBean 后,就可 以在 Spring MVC 入参绑定及模型数据输出时使用注解驱动 了。
对属性对象的输入/输出进行格式化,从其本质上讲依然 属于 “类型转换” 的范畴。
Spring 在格式化模块中定义了一个实现ConversionService 接口的FormattingConversionService 实现类,该实现类扩展了 GenericConversionService,因此它既具有类型转换的 功能,又具有格式化的功能
FormattingConversionService 拥有一个FormattingConversionServiceFactroyBean 工厂类, 后者用于在 Spring 上下文中构造前者。
FormattingConversionServiceFactroyBean 内部已经注册了 :
–NumberFormatAnnotationFormatterFactroy:支持对数字类型的属性使用 @NumberFormat 注解
–JodaDateTimeFormatAnnotationFormatterFactroy:支持对日期类型的属性使用 @DateTimeFormat 注解
日期格式化
@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:完整 日期/时间格式、-:忽略日期或时间格式
数值格式化
@NumberFormat 可对类似数字类型的属性进行标 注,它拥有两个互斥的属性: style:类型为 NumberFormat.Style。用于指定样式类型,包括三种:Style.NUMBER(正常数字类型)、Style.CURRENCY(货币类型)、 Style.PERCENT( 百分数类型) pattern:类型为 String,自定义样式, 如patter="#,###";
格式化示例-在crud项目基础上
1、编辑转发配置器
<mvc:annotation-driven></mvc:annotation-driven>
2、编辑表单
<form:form action="${pageContext.request.contextPath }/emp" method="POST" modelAttribute="employee"> <!-- 其他表单项已经省略,主要看对 date 和 salary 表单项的格式化 --> <!-- 1. 数据类型转换 2. 数据类型格式化 3. 数据校验. 1). 如何校验 ? 注解 ? ①. 使用 JSR 303 验证标准 ②. 加入 hibernate validator 验证框架的 jar 包 ③. 在 SpringMVC 配置文件中添加 <mvc:annotation-driven /> ④. 需要在 bean 的属性上添加对应的注解 ⑤. 在目标方法 bean 类型的前面添加 @Valid 注解 2). 验证出错转向到哪一个页面 ? 注意: 需校验的 Bean 对象和其绑定结果对象或错误对象时成对出现的,它们之间不允许声明其他的入参 3). 错误消息 ? 如何显示, 如何把错误消息进行国际化 --> Birth: <form:input path="birth"/> <br> Salary: <form:input path="salary"/> <br> <input type="submit" value="Submit"/> </form:form>
3、编辑entity中的employee.java
###### 属性注解 ###### @DateTimeFormat(pattern="yyyy-MM-dd") private Date birth; @NumberFormat(pattern="#,###,###.#") private Float salary; ###### 属性方法 ###### public Date getBirth() { return birth; } public void setBirth(Date birth) { this.birth = birth; } public Float getSalary() { return salary; } public void setSalary(Float salary) { this.salary = salary; }
4、补充说明
前面三步操作,已经可以支持数据格式化了。如果想加入自定义转换器的话,需要修改一下转发配置器:
<!-- 配置 ConversionService ,下面由原来的 ConversionServiceFactoryBean 转变成 FormattingConversionServiceFactoryBean --> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <ref bean="employeeConverter"/> </set> </property> </bean> <mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
四、出错信息
数据格式类型转换出错信息
在上节的数据格式化中:
@DateTimeFormat(pattern=”yyyy-MM-dd”)
@NumberFormat(pattern=”#,###,###.#”)
这两个注解就是用来格式化的,如果表单中的数据,不符合要求格式时,校验框架会发现错误,并在请求的网页显示错误。但是控制台没有打印错误。这时我们可以在控制器添加错误解析代码就行:
@RequestMapping(value="/emp", method=RequestMethod.POST) public String save(Employee employee, BindingResult result, Map<String, Object> map){ System.out.println("save: " + employee); //验证 是否存在 错误信息 if(result.getErrorCount() > 0){ System.out.println("出错了!"); for(FieldError error:result.getFieldErrors()){ System.out.println(error.getField() + ":" + error.getDefaultMessage()); } //若验证出错, 则转向定制的页面 map.put("departments", departmentDao.getDepartments()); return "addEmp"; } employeeDao.save(employee); return "redirect:/emps"; }
- JSR 303 是 Java 为 Bean 数据合法性校验提供的标准, 它已经包含在 JavaEE 6.0 中 .
- JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max、@Past 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证
- Hibernate Validator 是 JSR 303 的一个参考实现,除支持 所有标准的校验注解外,它还支持以下的扩展注解 @Email,@Length,@NotEmpty,@Range
注意:@Past 代表属性是过去式 ,比如生日;@NotEmpty 代表属性不为空;这些类似的校验注解都需要添加相应的校验jar包。比如:
validation-api-1.1.0.CR1.jar
hibernate-validator-5.0.0.CR2.jar
hibernate-validator-annotation-processor-5.0.0.CR2.jar
添加完jar包后,实际运行时遇到:
Handler dispatch failed; nested exception is java.lang.NoClassDefFoundError: com/fasterxml/classmate/Filter
异常提示缺少类,需要导入jar包:需要导入hibernate中的classmate.jar即可
=========================================================
- Spring 4.0 拥有自己独立的数据校验框架,同时支持 JSR303 标准的校验框架。
- Spring 在进行数据绑定时,可同时调用校验框架完成数据校 验工作。在 Spring MVC 中,可直接通过注解驱动的方式 进行数据校验
- Spring 的 LocalValidatorFactroyBean 既实现了 Spring 的Validator 接口,也实现了 JSR 303 的 Validator 接口。只要 在 Spring 容器中定义了一个 LocalValidatorFactoryBean,即可将其注入到需要数据校 验的 Bean 中。
- Spring 本身并没有提供 JSR303 的实现,所以必须将JSR303 的实现者的 jar 包放到类路径下。
数据校验出错信息
@DateTimeFormat(pattern=”yyyy-MM-dd”)
@NumberFormat(pattern=”#,###,###.#”)
上面两个已经在 mvc-annotation中的FormattingConversionService 注册好了。不需要额外添加什么。
如果需要在javabean中,实现其他的校验功能
第一步:需要在bean的属性添加:@NotEmpty,@Email,@Past 等注解。如:
@NotEmpty private String lastName; @Email private String email;
第二步:添加jar包
validation-api-1.1.0.CR1.jar hibernate-validator-5.0.0.CR2.jar hibernate-validator-annotation-processor-5.0.0.CR2.jar classmate-1.0.0.jar
第三步:编辑控制器 (关键是在 形参bean上 添加 @Valid 注解)
//处理添加雇员请求,并重定向到 @RequestMapping("/emps") @RequestMapping(value="emp", method=RequestMethod.POST) public String addEmp(@Valid Employee employee ,BindingResult result, Map<String, Object> map){ System.out.println("save: " + employee); //验证 是否存在 错误信息 if(result.getErrorCount() > 0){ System.out.println("出错了!"); for(FieldError error:result.getFieldErrors()){ System.out.println(error.getField() + ":" + error.getDefaultMessage()); } //若验证出错, 则转向定制的页面 map.put("departments", departmentDao.getDepartments()); return "addEmp"; } employeeDao.save(employee); return "redirect:/emps"; }
结果:参考控制台 输出信息:
save: Employee [id=null, lastName=12, email=123, gender=0, department=Department [id=101, departmentName=null], birth=null, salary=null] 出错了! birth:Failed to convert property value of type 'java.lang.String' to required type 'java.util.Date' for property 'birth'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@javax.validation.constraints.Past @org.springframework.format.annotation.DateTimeFormat java.util.Date] for value '213'; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [213] salary:Failed to convert property value of type 'java.lang.String' to required type 'java.lang.Float' for property 'salary'; nested exception is java.lang.NumberFormatException: multiple points email:not a well-formed email address
补充:
<mvc:annotation-driven/> 会默认装配好一个LocalValidatorFactoryBean,通过在处理方法的入参上标 注 @valid 注解即可让 Spring MVC 在完成数据绑定后执行 数据校验的工作,并将校 验结果保存在被校验入参对象之后的 BindingResult 或 Errors 入参中。常用方法:
FieldError getFieldError(String field)
List getFieldErrors()
Object getFieldValue(String field)
Int getErrorCount()
Spring MVC 是通过对处理方法签名的规约来保存校验结果 的:前一个表单/命令对象的校验结果保存到随后的入参中,这个保存校验结果的入参必须是 BindingResult 或Errors 类型,这两个类都位于org.springframework.validation 包中
======================================================
需校验的 Bean 对象和其绑定结果对象或错误对象时成对出现的,它们之间不允许声明其他的入参。
//BindingResult 扩展了 Errors 接口。在控制器中,两种可以都可以用来放校验结果 //Errors 接口提供了获取错误信息的方法,如 getErrorCount() 或getFieldErrors(String field)。 public String handle 91(@Valid User user, BindingResult userBindingResult, String sessionId,ModelMap mm, @Valid Dept dept, Errors deptErrors){ }
页面展示错误信息
- Spring MVC 除了会将表单/命令对象的校验结果保存到对 应的 BindingResult 或 Errors 对象中外,还会将所有校验 结果保存到 “隐含模型”
- 即使处理方法的签名中没有对应于表单/命令对象的结果 入参,校验结果也会保存在 “隐含对象” 中。
- 隐含模型中的所有数据最终将通过 HttpServletRequest 的 属性列表暴露给 JSP 视图对象,因此在 JSP 中可以获取 错误信息
- 在 JSP 页面上可通过 <form:errors path=“userName”>显示错误消息
页面展示国际化出错信息
1、新建国际化文件
i18n.properties
NotEmpty.employee.lastName=LastName\u4E0D\u80FD\u4E3A\u7A7A. Email.employee.email=Email\u5730\u5740\u4E0D\u5408\u6CD5 ###Birth不能是一个将来的时间 Past.employee.birth=Birth\u4E0D\u80FD\u662F\u4E00\u4E2A\u5C06\u6765\u7684\u65F6\u95F4. ###########上面是数据校验 的错误信息,下面是类型转换 的错误信息############ ####Birth不是一个日期 typeMismatch.employee.birth=Birth\u4E0D\u662F\u4E00\u4E2A\u65E5\u671F.
数据校验错误key值:以校验注解类名为前缀+结合 modleAttribute+属性名属性类型名
比如key值:NotEmpty.employee.lastName
value值:LastName\u4E0D\u80FD\u4E3A\u7A7A.
类型转换错误key值:类型转换错误码+结合 modleAttribute+属性名属性类型名
比如key值:typeMismatch.employee.birth
value值:Birth\u4E0D\u662F\u4E00\u4E2A\u65E5\u671F.
类型转换错误:都会在隐含模型 中创建错误消息。其错误代码前缀说明如下:
–required:必要的参数不存在。如 @RequiredParam(“param1”)标注了一个入参,但是该参数不存在
–typeMismatch:在数据绑定时,发生数据类型不匹配的问题
–methodInvocation:Spring MVC 在调用处理方法时发生了错误
2、在转发配置器中注册国际化资源文件:
<!-- 配置国际化资源文件 --> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="i18n"></property> </bean>