基础流程
<form/>表单提交时,其是如何与实体类进行一一映射的呢?映射过程中是如何实现数据类型转换、格式化、校验的呢?
SpringMVC映射form表单过程如下:
①SpringMVC主框架将ServletRequest对象以及目标方法的入参实例传递给WebDataBinderFactory实例,来创建DataBinder实例对象;
②DataBinder调用装配在上下文中的ConversionService组件进行数据类型转换、数据格式化工作,将Servlet中的请求信息填充到入参对象中;
SpringMVC内建了很多转换器,可以完成不同数据类型之间的转换工作,接口时ConversionService,比如String转换成Boolean的StringToBooleanConverter;
③接着调用Validator组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果BindingData对象
④如果数据类型转换、数据格式化、校验过程中发生异常,SpringMVC抽取BindingResult中的入参对象和校验错误对象,将他们赋给处理方法的响应入参;
自定义类型转换器
ConversionServiceFactoryBean在Spring的IOC容器中创建一个ConversionService,然后Spring自动识别容器中的ConversionService,并在Bean属性配置以及SpringMVC处理方法入参绑定等场合使用该ConversionService来进行数据的转换;
可通过ConversionServiceFactoryBean的converters属性来注册自定义的类型转换器,而converters属性是Set类型;
SpringMVC一共定义了3种类型的转换器接口,实现任意一个转换器接口即可作为自定义转换器来注册到ConversionServiceFactoryBean中:
①Converter<S,T>:将S类型对象转换为T类型对象;
②ConverterFactory:将相同系列、多个同质Converter封装在一起,如果希望将一种类型的对象转换为另外一种类型及其子类的对象时,可以使用该转换器工厂类;
③GenericConverter:会根据源类对象以及目标类对象所在的宿主类的上下文信息来进行类型转换;
在实现的过程中通过
<mvc:annotation-driven conversion-service=”xx”/>来实现;
这里的指的是org.springframework.context.support.ConversionServiceFactoryBean的id,而ConversionServiceFactoryBean的converters属性里放着我们自定义的ConversionService;
<mvc:annotation-driver/>
<mvc:annotation-driver/>会自动注册RequestMappingHandlerMapping、RequestMappingHandlerAdapter、ExceptionHandlerExceptionResolver这3个bean,且还提供如下支持:
①支持使用ConversionService实例来对表单参数进行类型转换;
②支持使用@NumberFormatannotation、@DataTimeFormat注解来完成数据类型的格式化;
③支持使用@Valid注解对JavaBean实例进行JSR303验证;
④支持使用@RequestBody和@ResponseBody注解;
基本上SpringMVC项目开发时,都会配置这玩意;
@InitBinder
被@InitBinder注解的方法,可以对WebDataBinder对象进行初始化;WebDataBinder是DataBinder的子类,用于完成由表单字段到JavaBean属性的绑定;
@InitBinder注解的方法不能有返回值,且必须声明为void;
@InitBinder注解的方法的参数通常是WebDataBinder;
应用场景:当form表单的某个属性,无法直接映射到JavaBean时,则可以通过该注解,外加WebDataBinder.setDisallowedFields方法来禁止这个form表单属性的直接映射,然后再通过自定义的转换器进行转换;
数据格式化
如果想把form表单的字符串类型转到JavaBean的日期Date类型的属性上,那么可以在Date类型的属性上使用@DateTimeFormat注解;
如果想把form表单的字符串类型转到JavaBean的数字Number类型的属性上,那么可以在Number类型的属性上使用@NumberFormat注解;
原理如下:
Spring在格式化模块中定义了一个实现ConversionService接口的FormattingConversionService实现类,该实现类扩展了GenericConversionService,因此该实现类既具有类型转换的功能,还具有格式化的功能;
FormattingConversionService拥有一个FormattingConversionServiceFactoryBean工厂类,该工厂类位于Spring上下文中来创建FormattingConversionService实例;
FormattingConversionServiceFactoryBean内部已经注册了:
①NumberFormatAnnotationFormatterFactory:支持对数字类型的属性来使用@NumberFormat注解;
②JodaDateTimeFormatAnnotationFormatterFactory:支持对日期类型的属性使用@DateTimeFormat注解;
装配了FormattingConversionServiceFactoryBean之后,就可以在SpringMVC入参、模型属性输出时来使用注解驱动了;
而<mvc:annotation-driven/>默认创建的ConversionService的实例就是FormattingConversionServiceFactoryBean;
这里<mvc:annotation-driver/>的conversion-service属性,可以把ConversionServiceFactoryBean改成FormattingConversionServiceFactoryBean;
数据校验JSR303
JSR303是JAVA为Bean提供的数据合法性校验框架,已经被包含在JavaEE6.0中;
JSR303通过在Bean属性上使用注解来达到数据合法性校验的目的;
Hibernate Validator是对JSR303的一个扩展实现,除了支持标准的注解之外,自身还进行了一部分扩展;
Spring4.0拥有自己独立的数据校验框架,该框架也支持JSR303标准;Spring也是通过注解的方式来完成数据合法性校验;Spring的LocalValidatorFactoryBean同时实现了Spring的Validator接口、JSR303的Validator接口,因此只要在IOC容器中定义一个LocalValidatorFactoryBean就可以在JavaBean使用其注解;
注意:Spring本身没有提供JSR303的实现,因此必须将第三方JSR303实现的jar包导入到项目中;
<mvc:annotation-drivern/>会默认装配好一个LocalValidatorFactoryBean,通过在处理方法的入参上使用@valid注解就可以让SpringMVC在完成数据绑定、数据格式化之后执行数据合法性校验的工作(即入参的属性添加对应的注解,然后目标方法的入参上添加@valid注解);
在已经标注了JSR303注解的表单/命令对象前使用@valid,SpringMVC将请求参数绑定到该入参对象后,就会调用校验框架根据注解声明的校验规则来进行校验;
SpringMVC是通过对处理方法签名的规则来保存校验结果的:前一个表单/命令对象的校验结果保存到随后的入参中,这个保存校验结果的入参必须是BindingResult或者Errors类型,而这2个类型位于org.springframework.validation包中;
注意:需校验的Bean对象和其绑定结果对象/错误对象总是成对出现,且他们的彼此紧挨着,中间不能允许声明其他的入参;
Erros接口提供了获取错误信息的方法,例如getErrorCount、getFiledErrors(String filed),而BindingResult继承了Errors接口;
校验失败的BindingResult/Errors信息国际化
每个属性在数据绑定、数据校验发生错误时,都会生成一个对应的FieldError对象;
当某个属性校验失败后,校验框架会为该属性生成4个消息代码,这些代码以校验注解类的类名作为前缀,结合JavaBean类名、属性名、属性类型来生成多个对应的消息代码;
消息代码具体组成如下:
注解名(校验注解类的类名).JavaBean类名.属性名
注解名(校验注解类的类名).属性名
注解名(校验注解类的类名).属性类型
注解名(校验注解类的类名)
例如:根据第1种消息代码,可以编写如下:
NotEmpty.user.userName=userName不允许为空
这里的NotEmpty是@NotEmpty注解类的类名,而user则是User这个JavaBean的类名,userName则是User的某个属性名;
注意:这里的注解名是注解类的类名,大小写与注解类名完全一致,而JavaBean的类名,则首字母小写;
当使用SpringMVC标签显示错误消息时,SpringMVC先查看WEB上下文是否装配了对应的国际化消息,如果没有则显示默认的错误消息,否则使用国际化消息;
①新建i18n.properties文件,里面可以配置上述4个消息代码的某一种; ②配置i18n: <bean id=”messageSource” class=”org.springframework.context.support.ResourceBundleMessageSource”> <property name=”basename” value=”i18n”/> </bean> |
类型转换/格式化失败的BindingResult信息国际化
如果数据类型转换、数据格式转换时发生异常,有可能是参数不存在导致的,也有可能格式转换时发生错误,无论哪种情况,都会在隐含模型中创建错误消息,其错误代码前缀说明如下:
required:必要的参数不存在;
typeMismatch:在数据绑定时,发生数据类型不匹配问题;
methodInvocation:SpringMVC在调用处理方法时发生了错误;
消息代码具体组成如下:
前缀名.JavaBean类名.属性名
前缀名.属性名
前缀名.属性类型
前缀名
例如:按照typeMismatch前缀,可以编写如下:
typeMismatch.user.birth=birth不是合法日期
这里的typeMismatch是前缀名,而user则是User这个JavaBean的类名,birth则是User的某个属性名;
①新建i18n.properties文件,里面可以配置上述4个消息代码的某一种; ②配置i18n: <bean id=”messageSource” class=”org.springframework.context.support.ResourceBundleMessageSource”> <property name=”basename” value=”i18n”/> </bean> |
扩展:国际化
WEB在国际化应用时,其场景无非就是下面3种:
①根据浏览器语言设置来对浏览器展示内容进行本地化处理;
解决方案之一:使用JSTL的fmt标签
②在bean中获取国际化资源文件Locale对应的消息;
解决方案之一:在bean中注入ResourceBundleMessageSource的实例(@Autowired注解即可),在通过该实例的getMessage方法来获取配置在properties中的消息;
③通过超链接来切换Locale;
解决方案之一:配置LocalResolver和LocalChangeInterceptor,该方案原理如下:
第一步:客户端发送请求,请求中要带有一个name=locale的参数;
第二步:SpringMVC拦截下该参数,然后根据该参数创建Locale对象;
第三步:然后获取LocaleResolver对象,这2步都是由LocaleChangeInterceptor拦截器完成;
第四步:把Locae对象设置为Session的属性;
第五步:从Session中获取Locale对象,该工作由SessionLocaleResolver完成;
(注意:SessionLocaleResolver与LocaleChangeInterceptor的bean-id不能随便乱写,SessionLocaleResolver的bean-id可以写为:localeResolver,而LocaleChangeInterceptor直接注册到<mvc:interceptors>/)
第六步:用户程序使用使用Local对象;
第七步:SpringMVC在渲染ModelAndView时,会从session中获取Locale对象,然后对视图中的数据进行国际化;