SpringMVC 之数据转换和国际化
1. 数据绑定流程
- SpringMVC 主框架将 ServletRequest 对象及目标方法的入参实例传递给 WebDataBinderFactory
实例,以创建 DataBinder 实例对象; - DataBinder 调用装配在 SpringMVC 上下文中的 ConversionService 组件进行数据类型转换,
数据格式化工作; 将 Servlet 中的请求信息填充到入参对象中; - 调用 Validator 组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果
BindingData 对象; - Spring MVC 抽取 BindingResult 中的入参对象和校验错误对象,将它们赋给处理方法的响应入参;
- 总结: Spring MVC 通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中.
数据绑定的核心部件是 DataBinder.
2. Spring 支持的转换器
- Spring 定义了三种类型的转换器接口,实现任意一个转换接口都可以作为自定义转换器注册到
ConversionServiceFacotoryBean 中:Converter<S,T>
: 将 S 类型的对象转换为 T 类型对象;ConverterFactory
: 将相同系列多个"同质"Converter封装在一起; 将一种类型的对象转换为
另一种类型及其子类的对象.例如,将String转换为 Number 及 Number 子类Integer,Long,
Double等对象;GenericConverter
: 会根据源类对象及目标类对象所在的宿主类中的上下文信息进行类型转换;
2.1 自定义类型转换器
- ConversionService 是 Spring 类型转换体系的核心接口;
- 可以利用 ConversionServiceFatoryBean 在 Spring 的 IOC 容器中定义一个 ConversionService;
Spring 将自动识别出 IOC 容器中的 ConversionService, 并在 Bean 属性配置及 SpringMVC
处理方法入参绑定等场合使用它进行数据的转换; - 可通过 ConversionServiceFactoryBean 的 converters 属性注册自定义的类型转换器;
// 从 java.lang.String 转换为 POJO 类
// index.jsp
<form action="${pageContext.request.contextPath}/empConvert" method="post">
<!-- 姓名;邮箱;性别;部门 -->
<input type="text" name="employee" value="lisi;lisi@163.com;1;105"/>
<input type="submit" value="类型转换"/>
</form>
// EmployeeHanlder.java
@RequestMapping(value="/empConvert",method=RequestMethod.POST)
public String empConvert(@RequestParam("employee") Employee employee){
employeeService.save(employee);
return "redirect:/emps";
}
// EmployeeConverter.java
// 类型转换,将String类型转换为 Employee, 即自定义类型转换器
@Component
public class EmployeeConverter implements Converter<String,Employee>{
public Employee convert(String source){
Employee result = null;
if(null != resource){
// 将字符串分割
String[] empInfos = source.split(";");
if(null != empInfos && empInfos.length == 4){
result = new Employee();
result.setLastName(empInfos[0]);
result.setEmail(empInfos[1]);
// 将String 类型转换为 Integer 类型
result.setGender(Integer.parseInt(empInfos[2]));
Department department = new Department();
department.setId(Integer.parseInt(empInfos[3]));
result.setDepartment(department);
}
}
return result;
}
}
// SpringDispatcherServlet-servlet.xml
// 在SpringMVC配置文件中,注册自定义类型转换器
<!-- 引用转换器 -->
<mvc:annotation-driven conversion-service="conversionServiceFactoryBean">
</mvc:annotation-driven>
<!-- 注册 -->
<bean id="conversionServiceFactoryBean"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<!-- 第一种写法: 首字母小写Bean -->
<list>
<ref bean="employeeConverter"/>
</list>
<!-- 第二种写法: 全类名的写法
<list>
<bean class="cn.itcast.springmvc.converts.EmployeeConvert"></bean>
</list>
-->
</perperty>
</bean>
2.2 mvc:annotation-driven
<mvc:annotation-driven/>
会自动注册RequestMappingHandlerMapping
,
RequestMappingHandlerAdapter
与ExceptionHandlerExceptionResolver
三个Bean;- 还提供以下支持:
- 支持使用
ConversionService
实例对表单参数进行类型转换; - 支持使用
@NumberFormatannotation
,@DataTimeFormat
注解完成数据类型的格式化; - 支持使用
@Valie
注解对 JavaBean 实例进行 JSR 303 验证; - 支持使用
@RequestBody
和@ResponseBody
注解;
- 支持使用
2.3 @InitBinder
- 由
@InitBinder
标识的方法,可以对 WebDataBinder 对象进行初始化,WebDataBinder 是 DataBinder
的子类,用于完成由表单字段到 JavaBean 属性的绑定; @InitBinder
方法不能有返回值,它必须声明为 void;@InitBinder
方法的参数通常是 WebDataBinder
@InitBinder
public void initBinder(WebDataBinder webDataBinder){
// 不自动绑定对象中的 email 属性
webDataBinder.setDisallowedFields("email");
}
3. 数据格式化
- 对属性对象的输入/输出进行格式化,从其本质上讲依然属于"类型转换"的范畴;
- Spring 在格式化模块中定义了一个实现了
ConversionService
接口的FormattingConversionService
实现类,该实现类扩展了GenericConversionService
,因此它既具有类型转换的功能,也具有格式化
的功能; FormattingConversionService
拥有一个FormattingConversionServiceFactoryBean
工
厂类,后者用于在 Spring上下文中构造前者;FormattingConversionServiceFactoryBean
与ConversionServiceFactoryBean
比较ConversionService
只有数据转换功能,与ConversionServiceFactoryBean
对应;FormattingConversionService
具有数据转换和数据格式化功能,FormattingConversionServiceFactoryBean
既可以注册自定义的转换器,也可以注册自定义
的注解驱动器逻辑;
<mvc:annotation-driven>
标签内部默认创建的一个 conversionService 实例就是一个
FormattingConversionServiceFactoryBean;
// 日期类: @DateTimeFormat
// 数字类: @NumberFormat
public class Employee{
private Integer id;
private String lastName;
private String email;
// 更改输入的日期格式, 默认格式为 yyyy/MM/dd
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birth;
@NumberFormat(patter="#,###.##")
private Double salary;
get和set 方法
}
// SpringDispatcherServlet-servlet.xml
// 配置 FormattingConversionServiceFacotryBean
<mvc:annotation-driven conversion-service="conversionService"/>
<bean id="conversionService"
class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<!-- 自定义转换器 -->
<property name="converters">
<list>
<ref bean='employeeConverter'/>
</list>
</property>
</bean>
3.1 BindingResult
- BindingResult 是接口,继承Errors;
// 保存客户
@RequestMapping(value="/emp",method=RequestMethod.POST)
public String save(Employee employee,BindingResult bindingResult){
if(bindingResult.getErrorCount()>0){
List<FieldError> list = bindingResult.getFieldErrors();
for(FieldError fieldError : list){
System.out.println(fieldError.getField()+"\t"+fieldError.getCode());
}
throw new RuntimeException("录入信息出错!");
}
employeeService.save(employee);
return "redirect:/emps";
}
3.2 JSR 303 数据验证
- JSR303 是Java为Bean数据合法性校验提供的标准框架,它已经包含在 JavaEE6.0 中;
- JSR303 通过在 Bean 属性上标注类似于
@NotNull
,@Max
等标准的注解指定校验规则,并通过
标准的验证接口对 Bean 进行验证; - Hibernate Validator 是 JSR303 的一个具体实现,除支持所有标准的校验注解外,它还支持以下的
扩展注解@Email
: 被注释的元素必须是邮箱地址;@Length
: 被注释的字符串大小必须在指定的范围内;@NotEmpty
: 被注释的字符串必须非空;@Range
: 被注释的元素必须在合适的范围内;
- 在 SpringMVC 中,可直接通过注解驱动的方式进行数据校验;
- Spring 的 LocalValidatorFactoryBean, 既实现了 Spring 的 Validator 接口,也实现了
JSR303 的 Validator 接口.只要在 Spring 容器中定义了一个 LocalValidatorFactoryBean,
即可将其注入到需要数据校验的Bean中; - Spring 本身并没有提供JSR303的实现,所以必须将JSR的实现者的jar包放到类路径下;
// 步骤:
/*
* 1. 导入 HibetnateValidator 验证框架的 jar 包(5个):
* classmate-0.8.0.jar, hibernate-validator-5.0.0.CR2.jar,
* hibernate-validator-annotation-processor-5.0.0.CR2.jar,
* jboss-logging-3.1.1.GA.jar, validation-api-1.1.0.CR1.jar
*
* 2. 添加注解 <mvc:annotation-driven></mvc:annotation-driven>, 会默认装配好一个
* LocalValicatorFactoryBean
* 3. Employee.java 中添加 Field 验证要求的相关注解;比如名字不为空, Email格式要合法等;
* 4. Save 方法对应的 POJO 参数前面添加 @Valid;
* 5. 发生异常后,重新回到录入页面;
*/
// Employee.java
pubic class Employee{
private Integer id;
@NotEmpty
private String lastName; // 名字不能为空
@Email
private String email; // 校验Email格式
.....(略)
}
// EmployeeHandler.java
public String save(@Valid Employee employee,BindingResult bindingResult,
Map<String,Object> map){
if(bindingResult.getErrorCount()>0){
List<FieldError> list = bindingResult.getFieldErrors();
for(FieldError fieldError : list){
System.out.println(fieldError.getField()+"\t"
+fieldError.getDefaultMessage());
}
// 返回到登录页面
// 1. 查询出全部部门
map.put("departments",departmentService.getDepartments());
// 2. 查询出性别
map.put("genders",getGenderUtils());
return "input";
}
employeeService.save(employee);
return "redirect:/emps";
}
// 页面显示错误信息
// index.jsp
<form:form action="${pageContext.request.contextPath}/emp" method="post"
modelAttribute="employee">
lastName:<form:input path="lastName"/>
<form:errors path="lastName"></form:errors><br/>
email:<form:input path="email"/>
<form:errors path="email"></form:errors><br/>
<input type="submit" value="添加"/>
</form:form>
// 自定义错误信息
// SpringDispatcherServlet-servlet.xml 配置 国际化信息
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"></property>
</bean>
// 在src目录下,新建 i18n.properties
// 命名规则: 注解名+类名+需要限制的field
NotEmpty.employee.lastName=用户名不能为空
Email.employee.email=邮件地址格式不正确
3.3 SpringMVC 数据校验
- 需校验的 Bean 对象和其绑定结果对象或错误对象是成对出现的,它们之间不允许声明其他的参数;
- Errors 接口提供了获取错误信息的方法, 如
getErrorCount()
和getFieldErrors(String field)
- BindingResult 扩展了 Errors 接口;
4. 处理JSON
- 导入 jar 包:
jackson-annotations-2.7.0.jar
jackson-core-2.7.0.jar
jackson-databind-2.7.0.jar
- 编写目标方法,使其返回 JSON 对应的对象(POJO)或集合(Map);
- 在方法上添加
@ResponseBody
注解;
// 需求: 查询所有员工
// Demo.java
public class Demo{
@Autowired
private EmployeeService employeeService;
// 导入 jackson 的jar包,并且标注 @ResponseBody
// HttpConverter 会自动识别出以 JSON 格式返回给前台
@ResponseBody
@RequestMapping(value="/testJson",method=RequestMethod.GET)
public Collection<Employee> qryAllEmployee(){
// 直接返回 Collection 集合
return employeeService.getAll();
}
}
// index.jsp
<head>
<script type="text/javascript">
$(function(){
$("#testJson").click(function(){
var url=$(this).attr("href");
var args = {name:"zhangsan"};
$.post(url,args,function(data){
for(var i=0; i<data.length; i++){
var id = data[i].id;
var lastName = data[i].lastName;
alert(id+" "+lastName);
}
});
});
})
</script>
</head>
<body>
JSON 示例: <a id="testJson" href="${pageContext.request.contextPath}/testJson">
点击这里
</a>
</body>
4.1 HttpMessageConverter
HttpMessageConverter<T>
接口负责将请求信息转换为一个对象(类型为 T),或者将对象(类型为 T)
输出为响应信息;HttpMessageConverter<T>
接口定义的方法:- Boolean canRead(Class<?>clazz, MediaType mediaType): 表示转换器是否可以将请求信息
转换为 clazz 类型的对象,同时,指定支持 MIME 类型(text/html,application/json等); - Boolean canWrite(Class<?>clazz, MediaType mediaType): 表示转换器是否可以将 clazz
类型的对象写入到响应流中; - T read(Class<? extends T>clazz,HttpInputMessage msg): 将请求信息流转换为 T 类型的
对象; - void write(T t, MediaType contentType, HttpOutputMessage msg): 将 T 类型的对象
写到响应流中,同时,指定响应的媒体类型为 contentType;
- Boolean canRead(Class<?>clazz, MediaType mediaType): 表示转换器是否可以将请求信息
- 常见
HttpMessageConverter<T>
的实现类StringHttpMessageConverter
: 将请求信息转换为字符串;XmlAwareFormHttpMessageConverter
ByteArrayHttpMessageConverter
: 读写二进制数据;SourceHttpMessageConverter
: 读写javax.xml.transform.Source
类型的数据;
- Spring 中使用
HttpMessageConverter<T>
:- 使用
@RequestBody/@ResponseBody
对处理方法进行标注; - 使用
HttpEntity<T>/ResponseEntity<T>
作为处理方法的入参或返回值;
- 使用
- 当控制处理方法使用到
@RequestBody/@ResponseBody
或HttpEntity<T>/ResponseEntity<T>
时, Spring 首先根据请求头或响应头的 Accept 属性选择匹配的 HttpMessageConverter, 进而
根据参数类型或泛型类型的过滤得到匹配的 HttpMessageConverter, 若找不到可用的
HttpMessageConverter, 将报错; @RequestBody
和@ResponseBody
不需要成对出现;
// 示例一: @RequestBody
// index.jsp
<h2>注册</h2>
<form action="${pageContext.request.contextPath}/testRequestBody" method="post"
enctype="multipart/form-data">
用户名:<input type="text" name="username"/><br/>
密码: <input type="password" name="password"/><br/>
上传文件: <input type="file" name="upload"/><br/>
<input type="submit" value="上传"/><br/>
</form>
// Demo.java
@RequestMapping(value="/testRequestBody",method=RequestMethod.POST)
public String testRequestBody(@RequestBody String content){
System.out.println("请求内容为:"+content);
return "ok";
}
// 示例二: 下载 @ResponseEntity<byte[]>
// index.jsp
下载示例: <a href="${pageContext.request.contextPath}/testDownload">下载</a>
// Demo.java
@RequestMapping(value="/testDownload", method=RequestMethod.GET)
public ReponseEntity<byte[]> testDownload() throws IOException{
byte[] body = null;
FileInputStream input = new FileInputStream(new File("/Users/用户名/Documents/a.js"));
body = new byte[input.available()];
input.read(body);
input.close();
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition","attachment;filename=a.js");
HttpStatus statusCode = HttpStatus.OK;
ResponseEntity<byte[]> result = new ResponseEntity<byte[]>(body,headers,statusCode);
return result;
}
5. 国际化
AcceptHeaderLocaleResolver
: 根据 HTTP 请求头的Accept-Language
参数确定本地化类型,
如果没有显示定义本地化解析器,SpringMVC默认使用该解析器;CookieLocaleResolver
: 根据指定的 Cookie 值确定本地化类型;SessionLocaleResolver
: 根据 Session 中特定的属性确定本地化类型;LocaleChangeInterceptor
: 从请求参数中获取本次请求对应的本地化类型;
// 第一种方式
// src 目录下,新建 i18n.properties, i18n_en_US.properties, i18n_zh_CN.properties
// i18n.properties 和 i18n_en_US.properties
i18n.username=username
i18n.password=password
// i18n_zh_CN.properties
i18n.username=用户名
i18n.password=密码
// index.jsp
国际化:<a href="${pageContext.request.contextPath}/i18n"></a>
// ok.jsp
用户名: <fmt:message key="i18n.username"></fmt:message><br/>
密 码: <fmt:message key="i18n.password"></fmt:message><br/>
// SpringDispatcherServlet-servlet.xml 配置
<!-- 国际化配置 -->
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"></property>
</bean>
// Demo.java
@RequestMapping(value="/i18n",method=RequestMethod.GET)
public String test(){
return "ok";
}
// 第二种方式: 注入ResourceBundleMessageSource
// Demo.java
public class Demo{
@Autowired
private ResourceBundleMessageSource messageSource;
@RequestMapping(value="/i18n2",method=RequestMethod.GET)
public String test2(Locale locale){
String v1 = messageSource.getMessage("i18n.username",null,locale);
String v2 = messageSource.getMessage("i18n.password",null,locale);
System.out.println(v1+"...."+v2);
return "ok";
}
}
// 第三种方式, 配置 SessionLocalResolver + LocaleChangeInterceptor
// springDispatcherServlet-servlet.xml
<!-- 配置 SessionLocaleResolver -->
<bean id="localeResolver"
class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
</bean>
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
</mvc:interceptors>
// index.jsp
<h2>示例</h2>
<a href="${pageContext.request.contextPath}/test3?locale=zh_CN">中文</a><br/>
<a href="${pageContext.request.contextPath}/test3?locale=en_US">英文</a><br/>
// Demo.java
public class Demo{
@RequestMapping(value="/test3",method=RequestMethod.GET)
public String test3(){
return "ok";
}
}
5.1 SessionLocaleResolver & LocaleChangeInterceptor 的工作原理
参考资料
SpringMVC 之数据转换和国际化的更多相关文章
- springMVC源码分析--国际化实现Session和Cookie(二)
上一篇博客springMVC源码分析--国际化LocaleResolver(一)中我们介绍了springMVC提供的国际化的解决方案,接下来我们根据springMVC提供的解决方案来简单的实现一个多语 ...
- springMVC之本地化和国际化
spring框架的大部分都支持国际化,就像springMVC一样.DispatcherServlet使你能够动态的通过客户端的本地语言进行配置.这是通过LocaleResolver完成的. 当一个 ...
- SpringMVC的数据转换,格式化和数据校验
在SpringMVC中,根据请求方法签名不同,将请求消息中的消息以一定的方式转换并绑定到请求方法的参数中,在请求信息到达真正调用处理方法的这一段时间内,SpringMVC还会完成很多其他的 ...
- springMVC源码分析--国际化LocaleResolver(一)
springMVC给我们提供了国际化支持,简单来说就是设置整个系统的运行语言,然后根据系统的运行语言来展示对应语言的页面,一般我们称之为多语言.springMVC国际化机制就是可以设置整个系统的运行语 ...
- SpringMVC的数据转换&&数据格式化&&数据校验
1 SpringMVC的数据绑定流程 SpringMVC将ServletRequest对象及目标方法的入参实例传递给WebDataBinderFactory实例,以创建DataBinder实例对象. ...
- SpringMVC(8)国际化
在SpringMVC(七)格式化显示中我们讲了数据的格式化显示,Spring在做格式化展示的时候已经做了国际化处理,那么如何将我们网站的其它内容(如菜单.标题等)做国际化处理呢?这就是本篇要将的内容- ...
- SpringMVC中数据转换
SpringMVC中接收到的数据都是String形式,然后再根据反射机制将String转换成对应的类型.如果此时想接收一个Date类型,那么我们可以定义一个转换器来完成. 例如,我们有下面的Emp类: ...
- 学习SpringMVC——国际化+上传+下载
每个星期一道菜,这个星期也不例外~~~ 一个软件,一个产品,都是一点点开发并完善起来的,功能越来越多,性能越来越强,用户体验越来越好……这每个指标的提高都需要切切实实的做点东西出来,好比,你的这个产品 ...
- SpringMVC国际化与文件上传
点击阅读上一章 其实SpringMVC中的页面国际化与上一章的验证国际化基本一致. 1.对页面进行国际化 1)首先我们对Spring配置文件中添加国际化bean配置 <!-- 注册国际化信息,必 ...
随机推荐
- Struts2动作
ActionContext 每一个请求的处理都在一个独立的线程中.每一个线程都有一个ActionContext对象.它包括了ValueStack和HttpServletRequest的东西. Stru ...
- application/x-www-form-urlencoded和multipart/form-data的区别
在学习<form>元素时,enctype属性有三个值 enctype属性表格: 值 描述 application/x-www-form-urlencoded 在发送前编码所有字符(默认) ...
- C# tif文件转jpg
需要添加WindowBase,PresentationCore的引用. 代码如下: private Stream GetImageStream() { //可以通过网络或本地文件的形式,返回Tif文件 ...
- 关于 web中 使用 java.net.URLEncoder.encode 要编码两次呢 , js的encodeURIComponent 同理
因为在jsp中对中文进行了编码的时候用的是UTF-8的编码方式,而在servlet中调用request.getParameter();方法的时候使用服务器指定的原始编码格式(ISO-8859-1)自动 ...
- PL/SQL开发五年工作经验精典实例
1. minus(差集)与intersect(交集) minus指令是运用在两个SQL语句上.它先找出第一个SQL语句所产生的结果,然后看这些结果有没有在第二个SQL语句的结果中,如果有的话,那这一笔 ...
- cocos2dx错误收集
1.读取ccb文件onNodeLoaded调用两次的问题 不小心把cocosbuilder里的控件的Custom class里填了两次自定义类,如下: 结果在onNodeLoaded时调用了两次,结果 ...
- Activiti(一)--安装配置具体解释
有一段时间没有更新文章了,尽管有一直在写文章,但是一直没有更新到博客内,这段时间写的文章大多还是以技术为主. 接下来的系列文章将会来讨论企业工作流的开发,主要是来研究开源工作流Activiti的使用. ...
- HLJU 1042 Fight (种类并查集)
1042: Fight Time Limit: 1 Sec Memory Limit: 128 MB Submit: 26 Solved: 8 [Submit][Status][pid=1042& ...
- 【BZOJ】3299: [USACO2011 Open]Corn Maze玉米迷宫(bfs)
http://www.lydsy.com/JudgeOnline/problem.php?id=3299 映射一下传送门即可.. #include <cstdio> #include &l ...
- 关于 HTML5 的文件上传处理,兼容性问题,以及 BLOB 对象的使用 (转载)
研究过程中关于本主体的相关参考 好文:https://hacks.mozilla.org/2011/01/how-to-develop-a-html5-image-uploader/ 好文:http: ...