1. 提出问题
2. 数据绑定流程原理★
① Spring MVC 主框架将 ServletRequest 对象及目标方法的入参实例传递给 WebDataBinderFactory 实例,以创建 DataBinder 实例对象
② DataBinder 调用装配在 Spring MVC 上下文中的 ConversionService 组件进行数据类型转换、数据格式化工作。将 Servlet 中的请求信息填充到入参对象中
③ 调用 Validator 组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果 BindingData 对象
④ Spring MVC 抽取 BindingResult 中的入参对象和校验错误对象,将它们赋给处理方法的响应入参
Spring MVC 通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是 DataBinder,运行机制如下:
3. 通过debug调试流程
① 查看数据绑定流程,在Employee类的set方法上设置断点调试
② Spring MVC 上下文中内建了很多转换器,可完成大多数 Java 类型的转换工作。
ConversionService converters =
java.lang.Boolean->java.lang.String: org.springframework.core.convert.support.ObjectToStringConverter@f874ca
java.lang.Character -> java.lang.Number : CharacterToNumberFactory@f004c9
java.lang.Character -> java.lang.String : ObjectToStringConverter@68a961
java.lang.Enum -> java.lang.String : EnumToStringConverter@12f060a
java.lang.Number -> java.lang.Character : NumberToCharacterConverter@1482ac5
java.lang.Number -> java.lang.Number : NumberToNumberConverterFactory@126c6f
java.lang.Number -> java.lang.String : ObjectToStringConverter@14888e8
java.lang.String -> java.lang.Boolean : StringToBooleanConverter@1ca6626
java.lang.String -> java.lang.Character : StringToCharacterConverter@1143800
java.lang.String -> java.lang.Enum : StringToEnumConverterFactory@1bba86e
java.lang.String -> java.lang.Number : StringToNumberConverterFactory@18d2c12
java.lang.String -> java.util.Locale : StringToLocaleConverter@3598e1
java.lang.String -> java.util.Properties : StringToPropertiesConverter@c90828
java.lang.String -> java.util.UUID : StringToUUIDConverter@a42f23
java.util.Locale -> java.lang.String : ObjectToStringConverter@c7e20a
java.util.Properties -> java.lang.String : PropertiesToStringConverter@367a7f
java.util.UUID -> java.lang.String : ObjectToStringConverter@112b07f ……
③查看:StringToNumberConverterFactory源码,在getConverter()方法中设置断点,在执行set方法(性别字段)前会调用该方法。
final class StringToNumberConverterFactory implements ConverterFactory<String, Number> {
@Override
public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToNumber<T>(targetType);
}
private static final class StringToNumber<T extends Number> implements Converter<String, T> {
private final Class<T> targetType;
public StringToNumber(Class<T> targetType) {
this.targetType = targetType;
}
@Override
public T convert(String source) {
if (source.length() == 0) {
return null;
}
return NumberUtils.parseNumber(source, this.targetType);
}
}
}
4. 自定义类型转换器
1) 类型转换器概述
ConversionService 是 Spring 类型转换体系的核心接口。
可以利用 ConversionServiceFactoryBean 在 Spring 的 IOC 容器中定义一个ConversionService. Spring 将自动识别出 IOC 容器中的 ConversionService,并在 Bean 属性配置及 Spring MVC 处理方法入参绑定等场合使用它进行数据的转换
可通过 ConversionServiceFactoryBean 的 converters 属性注册自定义的类型转换器
例如:
2) Spring 支持的转换器类型
Spring 定义了 3 种类型的转换器接口,实现任意一个转换器接口都可以作为自定义转换器注册到 ConversionServiceFactoryBean 中:
Converter
:将 S 类型对象转为 T 类型对象ConverterFactory:将相同系列多个 “同质” Converter 封装在一起。如果希望将一种类型的对象转换为另一种类型及其子类的对象(例如将 String 转换为 Number 及 Number 子类(Integer、Long、Double 等)对象)可使用该转换器工厂类
GenericConverter:会根据源类对象及目标类对象所在的宿主类中的上下文信息进行类型转换
[
](https://blog.csdn.net/qq_43284469/article/details/111172764)
3) 自定义转换器示例
需求:字符串转换为对象。
步骤:
① 定义页面
<form action="empAdd" method="POST">
<!-- 解决问题:
1.数据类型转换
2.数据格式
3.数据校验
自定义类型转换器:
将字符串转换为Employee对象,完成添加功能
BirthDay :<input type="text" name="birthDay"/><br><br>
-->
<!-- 字符串格式:lastName-email-gender-department.id
例如:GG-gg@atguigu.com-0-105
-->
Employee : <input type="text" name="employee"/>
<input type="submit" value="Submit"><br><br>
</form>
② 控制器方法
@Controller
public class TypeConversionHandler {
@Autowired
private EmployeeDao employeeDao ;
// String -> Employee 需要类型转换器帮忙
@RequestMapping("/empAdd")
public String empAdd(@RequestParam(value="employee") Employee employee){
System.out.println("TypeConversionHandler - " + employee);
employeeDao.save(employee);
return "redirect:/empList";
}
}
③ 自定义类型转换器
/**
* 将字符串转换为Employee对象类型
*/
@Component
public class StringToEmployeeConverter implements Converter<String, Employee> {
@Override
public Employee convert(String source) {
if(source!=null){
String[] strs = source.split("-");
if(strs!=null && strs.length == 4){
String lastName = strs[0];
String email = strs[1];
Integer gender = Integer.parseInt(strs[2]);
Integer deptId = Integer.parseInt(strs[3]);
Department dept = new Department();
dept.setId(deptId);
Employee employee = new Employee(null,lastName,email,gender,dept);
System.out.println(source+"--converter--"+employee);
return employee ;
}
}
return null;
}
}
④ 声明类型转换器服务
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<!-- 引用类型转换器 -->
<ref bean="stringToEmployeeConverter"/>
</set>
</property>
</bean>
⑤
ConversionService 注册到 Spring MVC 的上下文中
5. debug调试
① 增加新的转换器,之前的转换器是否还好使呢?好使。
查看,框架出厂设置,与目前将我们的自定义类型转换器加入出厂设置中
② 当配置了mvc:annotation-driven/后,会自动加载
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
和org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
6. < mvc:annotation-driven />配置在什么时候必须配置?
① 直接配置响应的页面:无需经过控制器来执行结果 ;但会导致其他请求路径失效,需要配置mvc:annotation-driven标签
<mvc:view-controller path="/success" view-name="success"/>
② RESTful-CRUD操作,删除时,通过jQuery执行delete请求时,找不到静态资源,需要配置mvc:annotation-driven标签
< mvc:default-servlet-handler /> 将在 SpringMVC 上下文中定义一个 DefaultServletHttpRequestHandler,它会对进入 DispatcherServlet 的请求进行筛查,如果发现是没有经过映射的请求,就将该请求交由 WEB 应用服务器默认的 Servlet 处理,如果不是静态资源的请求,才由 DispatcherServlet 继续处理。
③ 配置类型转换器服务时,需要指定转换器服务引用
ConversionService 注册到 Spring MVC 的上下文中
7. 关于 作用
RequestMappingHandlerMapping 、RequestMappingHandlerAdapter 与
ExceptionHandlerExceptionResolver 三个bean。
还将提供以下支持:
支持使用 ConversionService 实例对表单参数进行类型转换
支持使用 @NumberFormat、**@DateTimeFormat** 注解完成数据类型的格式化
支持使用 @Valid 注解对 JavaBean 实例进行 JSR 303 验证
支持使用 @RequestBody 和 @ResponseBody 注解
合源码分析(在bean对象的set方法上设置断点进行调试)
① 既没有配置 mvc:default-servlet-handler/ 也没有配置 mvc:annotation-driven/
都没有配置情况下,AnnotationMethodHandlerAdapter是默认出厂设置,干活的(过期)。
另外:conversionService是null(类型转换器是不起作用的)**
四月 30, 2016 3:52:21 下午 org.springframework.web.servlet.PageNotFound noHandlerFound 警告: No mapping found for HTTP request with URI [/SpringMVC_03_RESTFul_CRUD/scripts/jquery-1.9.1.min.js] in DispatcherServlet with name ‘springDispatcherServlet’
② 配置了 < mvc:default-servlet-handler/ > 但没有配置 < mvc:annotation-driven/ >
AnnotationMethodHandlerAdapter被取消,解决了静态资源查找,但是@RequestMapping不好使了。
③ 既配置了 < mvc:default-servlet-handler /> 又配置 < mvc:annotation-driven />【重要】
AnnotationMethodHandlerAdapter被替换成RequestMappingHandlerAdapter来干活了。
如果没有配置mvc:annotation-driven/**标签时, conversionService为null.
AnnotationMethodHandlerAdapter已经过时,Spring3.2推荐RequestMappingHandlerAdapter来替代。所以说,默认情况下,没有配置这两个配置时,HelloWorld 程序可以正常运行,但是,涉及到静态资源查找的时候,就必须配置这个< mvc:annotation-driven />配置了
8. InitBinder注解
① @InitBinder
由 @InitBinder 标识的方法,可以对 WebDataBinder 对象进行初始化。WebDataBinder 是 DataBinder 的子类,用于完成由表单字段到 JavaBean 属性的绑定
@InitBinder方法不能有返回值,它必须声明为void。
@InitBinder方法的参数通常是 WebDataBinder② 实验代码
/**
由 @InitBinder 标识的方法,可以对 WebDataBinder 对象进行初始化。
WebDataBinder 是 DataBinder 的子类,用于完成由表单字段到 JavaBean 属性的绑定
@InitBinder方法不能有返回值,它必须声明为void。
@InitBinder方法的参数通常是 WebDataBinder
*/
@InitBinder
public void initBinder(WebDataBinder dataBinder){
dataBinder.setDisallowedFields("lastName");
}
9. 数据的格式化
1) 数据格式化概述
对属性对象的输入/输出进行格式化,从其本质上讲依然属于 “类型转换” 的范畴。
Spring 在格式化模块中定义了一个实现 ConversionService 接口的FormattingConversionService 实现类,该实现类扩展了 GenericConversionService,因此它既具有类型转换的功能,又具有格式化的功能
FormattingConversionService 拥有一个 FormattingConversionServiceFactroyBean 工厂类,后者用于在 Spring 上下文中构造前者,FormattingConversionServiceFactroyBean 内部已经注册了 :
NumberFormatAnnotationFormatterFactroy:支持对数字类型的属性使用
@NumberFormat 注解
JodaDateTimeFormatAnnotationFormatterFactroy:支持对日期类型的属性使用
@DateTimeFormat 注解
装配了 FormattingConversionServiceFactroyBean 后,就可以在 Spring MVC 入参绑定及模型数据输出时使用注解驱动了。
< mvc:annotation-driven /> 默认创建的 ConversionService 实例即为
DefaultFormattingConversionService
2) 日期格式化概述
@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:完整日期/时间格式、-:忽略日期或时间格式
3) 数值格式化概述
@NumberFormat 可对类似数字类型的属性进行标注,它拥有两个互斥的属性:
style:类型为 NumberFormat.Style。用于指定样式类型,包括三种:Style.NUMBER(正常数字类型)、 Style.CURRENCY(货币类型)、 Style.PERCENT(百分数类型)
pattern:类型为 String,自定义样式,如pattern=”#,###”;
[
](https://blog.csdn.net/qq_43284469/article/details/111172764)
<!-- 声明类型转换器服务 -->
<!-- <bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean"> -->
<bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<!-- 引用类型转换器 -->
<ref bean="stringToEmployeeConverter"/>
</set>
</property>
</bean>
4) 实验代码(格式化日期)
① 页面表单
<!-- 解决问题:
1.数据类型转换
2.数据格式
3.数据校验
自定义类型转换器:
将字符串转换为Employee对象,完成添加功能
-->
BirthDay :<input type="text" name="birthDay"/><br><br>
② Employee类增加日期对象属性
//关于类型转换
private Date birthDay ;
③ 关于格式错误(框架默认支持的格式为斜线方式。1990/09/09)
在页面上设置格式为:1990-09-09
报错:
④ 解决400错误:
在Employee类的日期属性上增加
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birthDay ;
⑤配置,配置时不能指定conversion-service属性,否则,依然报错400。
用FormattingConversionServiceFactoryBean替换ConversionServiceFactoryBean后再进行引用。
<mvc:annotation-driven conversion-service="conversionService"/>
<mvc:annotation-driven />
5) 实验代码(格式化数字)
Salary : <form:input path="salary"/>
@NumberFormat(pattern="#,###,###.#") //10,000,000,0
private double salary ;
6) 如果类型转换失败,如何获取错误消息
(后台获取错误消息,并打印)
//添加员工
@RequestMapping(value="/empAdd",method=RequestMethod.POST)
public String empAdd(Employee employee,BindingResult bindingResult){
System.out.println("empAdd - employee="+employee);
if(bindingResult.getErrorCount() > 0 ){
System.out.println("类型转换处错误了");
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
for(FieldError fieldError : fieldErrors){
System.out.println(fieldError.getField() + " - " + fieldError.getDefaultMessage());
}
}
employeeDao.save(employee);
return "redirect:/empList";
}
类型转换出错误了
birthDay - Failed to convert property value of type 'java.lang.String' to required type 'java.util.Date' for property 'birthDay'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type java.lang.String to type @org.springframework.format.annotation.DateTimeFormat java.util.Date for value 's'; nested exception is java.lang.IllegalArgumentException: Unable to parse 's'
salary - Failed to convert property value of type 'java.lang.String' to required type 'double' for property 'salary'; nested exception is java.lang.NumberFormatException: For input string: "ss"