① Spring MVC 主框架将 ServletRequest 对象及目标方法的入参实例传递给 WebDataBinderFactory 实例,以创建 DataBinder 实例对象
② DataBinder 调用装配在 Spring MVC 上下文中的ConversionService 组件进行数据类型转换、数据格式化工作。将 Servlet 中的请求信息填充到入参对象中
③ 调用 Validator 组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果BindingData 对象
④ Spring MVC 抽取 BindingResult 中的入参对象和校验错误对象,将它们赋给处理方法的响应入参
1.1. 运行机制
Spring MVC 通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是DataBinder,运行机制如下:
1.2. 源码分析
1.3. ConversionService 类型转换器
① ConversionService 是 Spring 类型转换体系的核心接口。
②可以利用 ConversionServiceFactoryBean 在Spring的IOC容器中定义一个 ConversionService. Spring 将自动识别出IOC 容器中的 ConversionService,并在 Bean 属性配置及Spring MVC 处理方法入参绑定等场合使用它进行数据的转换
③ 可通过 ConversionServiceFactoryBean 的 converters 属性注册自定义的类型转换器
1.4. 自定义类型转换器
Spring 定义了 3 种类型的转换器接口,实现任意一个转换器接口都可以作为自定义转换器注册到
ConversionServiceFactroyBean 中:
– Converter<S,T>:将 S 类型对象转为 T 类型对象
– ConverterFactory:将相同系列多个 “同质” Converter 封装在一起。如果希望将一种类型的对象转换为另一种类型及其子类的对象(例如将 String 转换为 Number 及 Number 子类(Integer、Long、Double 等)对象)可使用该转换器工厂类
– GenericConverter:会根据源类对象及目标类对象所在的宿主类中的上下文信息进行类型转换
现在自己实现一个类型转换器将字符串转换成员工
spring-mvc.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd 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-4.1.xsd"> <context:component-scan base-package="com.ibigsea.springmvc"/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:default-servlet-handler/> <mvc:annotation-driven conversion-service="converterService"/> <bean id="converterService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <list> <bean class="com.ibigsea.springmvc.conversion.DepartmentConversion"></bean> </list> </property> </bean> </beans>
DepartmentConversion.java
package com.ibigsea.springmvc.conversion; import org.springframework.core.convert.converter.Converter; import com.ibigsea.springmvc.model.Department; public class DepartmentConversion implements Converter<String, Department>{ /** * 传入的数据格式 * xx-xx */ @Override public Department convert(String data) { if (data == null) { return null; } String [] args = data.split("-"); if (args!=null && args.length != 2) { return null; } return new Department(Integer.parseInt(args[0]), args[1]); } }
ConversionController.java
package com.ibigsea.springmvc.rest; import java.util.Map; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import com.ibigsea.springmvc.model.Employee; @Controller public class ConversionController { @RequestMapping("/conversion") public String toConversionData(){ return "conversionData"; } @RequestMapping("/addemp") public String addEmp(Employee emp,Map<String, Object> map){ map.put("emp", emp); return "conversionData"; } }
conversionData.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <form action="addemp" method="post"> <input type="hidden" name="id" ><br/><br/> name : <input type="text" name="name" ><br/><br/> email : <input type="text" name="email" ><br/><br/> sex :<input type="radio" name="sex" value="0" >男 <input type="radio" name="sex" value="1" >女<br/><br/> department : <input type="text" name="department" ><br><br> <input type="submit" value="提交"> </form> ${emp} </body> </html>
1.5. 数据格式化
对属性对象的输入/输出进行格式化,从其本质上讲依然属于 “类型转换” 的范畴。
① Spring 在格式化模块中定义了一个实现ConversionService 接口的
FormattingConversionService 实现类,该实现类扩展了 GenericConversionService,因此它既具有类型转换的功能,又具有格式化的功能
② FormattingConversionService 拥有一个 工厂类,后者用于在 Spring 上下文中构造前者
FormattingConversionServiceFactroyBean 内部已经注册了 :
– NumberFormatAnnotationFormatterFactroy:支持对数字类型的属性使用 @NumberFormat 注解
–JodaDateTimeFormatAnnotationFormatterFactroy:支持对日期类型的属性使用 @DateTimeFormat 注解
? 装配了 FormattingConversionServiceFactroyBean 后,就可以在 Spring MVC 入参绑定及模型数据输出时使用注解驱动了。<mvc:annotation-driven/> 默认创建的ConversionService 实例即为FormattingConversionServiceFactroyBean
如果自定义了类型转换器 只用将Class配置为FormattingConversionServiceFactroyBean即可
spring-mvc.xml:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd 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-4.1.xsd"> <context:component-scan base-package="com.ibigsea.springmvc"/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/pages/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:default-servlet-handler/> <mvc:annotation-driven conversion-service="converterService"/> <bean id="converterService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <list> <bean class="com.ibigsea.springmvc.conversion.DepartmentConversion"></bean> </list> </property> </bean> </beans>
1.5.1. @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:完整日期/时间格式、-:忽略日期或时间格式
1.5.2. @NumberFormat
@NumberFormat 可对类似数字类型的属性进行标
注,它拥有两个互斥的属性:
– style:类型为 NumberFormat.Style。用于指定样式类型,包括三种:Style.NUMBER(正常数字类型)、Style.CURRENCY(货币类型)、 Style.PERCENT(百分数类型)
– pattern:类型为 String,自定义样式,如patter="#,###";
1.5.3. FormattingConversionService转换
1.6. JSR 303 校验
JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 中 .
? JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max等标准的注解指定校验规则,并通过标准的验证接口对 Bean进行验证
1.6.1. Hibernate Validator 扩展注解
在pom.xml 中加入
<dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.0.2.Final</version> </dependency>
Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解
1.6.2. SpringMVC 数据校验
Spring 4 拥有自己独立的数据校验框架,同时支持 JSR303 标准的校验框架。
① Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作。在 Spring MVC 中,可直接通过注解驱动的方式进行数据校验
② Spring 的 LocalValidatorFactroyBean 既实现了 Spring 的Validator 接口,也实现了 JSR 303的Validator 接口。只要在 Spring 容器中定义了一个LocalValidatorFactoryBean,即可将其注入到需要数据校验的 Bean 中。
③ Spring 本身并没有提供 JSR303 的实现,所以必须将JSR303 的实现者的 jar 包放到类路径下。
<mvc:annotation-driven/> 会默认装配好一个LocalValidatorFactoryBean,通过在处理方法的入参上标注 @valid 注解即可让 Spring MVC 在完成数据绑定后执行数据校验的工作
在已经标注了 JSR303 注解的表单/命令对象前标注一个@Valid,Spring MVC 框架在将请求参数绑定到该入参对象后,就会调用校验框架根据注解声明的校验规则实施校验
Spring MVC 是通过对处理方法签名的规约来保存校验结果的:前一个表单/命令对象的校验结果保存到随后的入参中,这个保存校验结果的入参必须是 BindingResult 或Errors 类型,这两个类都位于org.springframework.validation 包中