标题# 数据绑定流程分析
1. 提出问题
① 日期字符串格式的表单参数,提交后转换为Date类型
<!-- 解决问题:
1.数据类型转换
2.数据格式
3.数据校验
-->
BirthDay :<form:input path="birthDay"/>
Employee类中增加日期类型属性:
//关于类型转换
private Date birthDay ;
2. 数据绑定流程原理★
1.Spring MVC 主框架将 ServletRequest 对象及目标方法的入参实例传递给 WebDataBinderFactory 实例,
以创建 DataBinder 实例对象
2.DataBinder 调用装配在 Spring MVC 上下文中的 ConversionService 组件进行数据类型转换、数据格式化工作。
将 Servlet 中的请求信息填充到入参对象中
3. 调用 Validator 组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果 BindingData 对象
4. 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方法(性别字段)前会调用该方法。
package org.springframework.core.convert.support;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.ConverterFactory;
import org.springframework.util.NumberUtils;
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>:将 S 类型对象转为 T 类型对象
ConverterFactory:将相同系列多个 “同质” Converter 封装在一起。如果希望将一种类型的对象转换为另一种类型及其子类的对象(例如将 String 转换为 Number 及 Number 子类(Integer、Long、Double 等)对象)可使用该转换器工厂类
GenericConverter:会根据源类对象及目标类对象所在的宿主类中的上下文信息进行类型转换
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>
② 控制器方法
package com.atguigu.springmvc.crud.handlers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.atguigu.springmvc.crud.dao.EmployeeDao;
import com.atguigu.springmvc.crud.entities.Employee;
@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";
}
}
③ 自定义类型转换器
package com.atguigu.springmvc.converter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
import com.atguigu.springmvc.crud.entities.Department;
import com.atguigu.springmvc.crud.entities.Employee;
/**
* 将字符串转换为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>
⑤ <mvc:annotation-driven conversion-service=“conversionService”/> 会将自定义的
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 继续处理。
③ 配置类型转换器服务时,需要指定转换器服务引用
<mvc:annotation-driven conversion-service=“conversionService”/> 会将自定义的
ConversionService 注册到 Spring MVC 的上下文中
④ 后面完成JSR 303数据验证,也需要配置
7. 关于 <mvc:annotation-driven /> 作用
<mvc:annotation-driven /> 会自动注册:
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="#,###";
<!-- 声明类型转换器服务 -->
<!-- <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="#,###,###.#")
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"
10. JSR303数据校验
1) 如何校验
①使用JSR 303验证标准
②加入hibernate validator验证框架
③在SpringMVC配置文件中增加mvc:annotation-driven/
④需要在bean的属性上增加对应验证的注解
⑤在目标方法bean类型的前面增加@Valid注解
2) 验证出错后,跳转到哪个页面
3) 错误消息,如何显示,如何国际化
4) JSR 303
是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 中 .
JSR 303 (Java Specification Requests意思是Java 规范提案)通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证
5) Hibernate Validator 扩展注解
Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解
6) Spring MVC 数据校验
Spring 4.0 拥有自己独立的数据校验框架,同时支持 JSR 303 标准的校验框架。
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 框架在将请求参数绑定到该入参对象后,就会调用校验框架根据注解声明的校验规则实施校验
7) 实验代码
① 添加jar包:
hibernate-validator-5.0.0.CR2\dist
hibernate-validator-5.0.0.CR2.jar
hibernate-validator-annotation-processor-5.0.0.CR2.jar
hibernate-validator-5.0.0.CR2\dist\lib\required (EL就不需要加了)
classmate-0.8.0.jar
jboss-logging-3.1.1.GA.jar
validation-api-1.1.0.CR1.jar
② 在验证属性上增加验证注解
public class Employee {
private Integer id;
@NotEmpty
private String lastName;
@Email
private String email;
//1 male, 0 female
private Integer gender;
private Department department;
//关于类型转换
@Past //被标注的日期必须是一个过去的日期
@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birthDay ;
@NumberFormat(pattern="#,###,###.#")
private double salary ;
}
③ 增加
//添加员工
/** 增加@Valid注解,验证失败会报错。
* 严重: Servlet.service() for servlet springDispatcherServlet threw exception
java.lang.NoSuchMethodError: javax.el.ExpressionFactory.newInstance()Ljavax/el/ExpressionFactory;
*/
@RequestMapping(value="/empAdd",method=RequestMethod.POST)
public String empAdd(@Valid 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";
}
④ 后台打印错误消息
⑤ 前台打印错误消息
⑥ 测试验证,解决EL表达式错误
拷贝hibernate-validator-5.0.0.CR2\dist\lib\required目录下的
el-api-2.2.jar、javax.el-2.2.4.jar、javax.el-api-2.2.4.jar
三个包到Tomcat/lib目录下,将原来的el-api.jar删除。重启tomcat6
⑦ 如果希望验证失败,回到添加页面
@RequestMapping(value="/empAdd",method=RequestMethod.POST)
public String empAdd(@Valid 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());
}
map.put("deptList",departmentDao.getDepartments());
return "add"; // /WEB-INF/views/add.jsp
}
employeeDao.save(employee);
return "redirect:/empList";
}
⑧ public interface BindingResult extends Errors
Spring MVC 是通过对处理方法签名的规约来保存校验结果的:前一个表单/命令对象的校验结果保存到随后的入参中,这个保存校验结果的入参必须是 BindingResult 或 Errors 类型,这两个类都位于 org.springframework.validation 包中
需校验的 Bean 对象和其绑定结果对象或错误对象是成对出现的,它们之间不允许声明其他的入参
Errors 接口提供了获取错误信息的方法,如 getErrorCount() 或 getFieldErrors(String field)
BindingResult 扩展了 Errors 接口
11. 错误消息的显示及国际化
1) 在页面上显示错误
Spring MVC 除了会将表单/命令对象的校验结果保存到对应的 BindingResult 或 Errors 对象中外,还会将所有校验结果保存到 “隐含模型”
即使处理方法的签名中没有对应于表单/命令对象的结果入参,校验结果也会保存在 “隐含对象” 中。
隐含模型中的所有数据最终将通过 HttpServletRequest 的属性列表暴露给 JSP 视图对象,因此在 JSP 中可以获取错误信息
在 JSP 页面上可通过 <form:errors path=“userName”> 显示错误消息
2) 示例:
① 在表单上页面上显示所有的错误消息
<!-- 显示所有的错误消息 -->
<form:errors path="*"/>
② 显示某一个表单域的错误消息
<form:errors path="lastName"/>
③ 有错,回到add.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" import="java.util.*"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!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>
<!--
1.为什么使用SpringMVC的form标签
① 快速开发
② 表单回显
2.可以通过modelAttribute指定绑定的模型属性,
若没有指定该属性,则默认从request域中查找command的表单的bean
如果该属性也不存在,那么,则会发生错误。
-->
<form:form action="empAdd" method="POST" modelAttribute="employee">
<!-- 显示所有的错误消息 --><form:errors path="*"/><br><br>
LastName : <form:input path="lastName" /> <form:errors path="lastName"/> <br><br>
Email : <form:input path="email" /><form:errors path="email"/><br><br>
<%
Map<String,String> map = new HashMap<String,String>();
map.put("1", "Male");
map.put("0","Female");
request.setAttribute("genders", map);
%>
Gender : <form:radiobuttons path="gender" items="${genders}" delimiter="<br>"/>
DeptName :
<form:select path="department.id"
items="${deptList }"
itemLabel="departmentName"
itemValue="id"></form:select><br><br>
<!-- 解决问题:
1.数据类型转换
2.数据格式
3.数据校验
1)-如何校验
验证格式,需要个注解就可以了
①使用JSR 303验证标准
②加入hibernate validator验证框架
③在SpringMVC配置文件中增加<mvc:annotation-driven/>
④需要在bean的属性上增加对应验证的注解
⑤在目标方法bean类型的前面增加@Valid注解
2)-验证出错后,跳转到哪个页面
3)错误消息,如何显示,如何国际化
自定义类型转换器:
将字符串转换为Employee对象,完成添加功能
-->
BirthDay :<%-- <input type="text" name="birthDay"/> --%>
<form:input path="birthDay"/><form:errors path="birthDay"/><br><br>
Salary : <form:input path="salary"/><br><br>
<input type="submit" value="Submit"><br><br>
</form:form>
</body>
</html>
12. 提示消息的国际化
每个属性在数据绑定和数据校验发生错误时,都会生成一个对应的 FieldError 对象。
当一个属性校验失败后,校验框架会为该属性生成 4 个消息代码,这些代码以校验注解类名为前缀,结合 modleAttribute、属性名及属性类型名生成多个对应的消息代码:例如 User 类中的 password 属性标注了一个 @Pattern 注解,当该属性值不满足 @Pattern 所定义的规则时, 就会产生以下 4 个错误代码:
Pattern.user.password
Pattern.password
Pattern.java.lang.String
Pattern
当使用 Spring MVC 标签显示错误消息时, Spring MVC 会查看 WEB 上下文是否装配了对应的国际化消息,如果没有,则显示默认的错误消息,否则使用国际化消息。
若数据类型转换或数据格式转换时发生错误,或该有的参数不存在,或调用处理方法时发生错误,都会在隐含模型中创建错误消息。其错误代码前缀说明如下:
required:必要的参数不存在。如 @RequiredParam(“param1”) 标注了一个入参,但是该参数不存在
typeMismatch:在数据绑定时,发生数据类型不匹配的问题
methodInvocation:Spring MVC 在调用处理方法时发生了错误
注册国际化资源文件
13. 提示消息的国际化实验
① 定义国际化资源文件:i18n.properties
NotEmpty.employee.lastName=\u7528\u6237\u540D\u4E0D\u80FD\u4E3A\u7A7A
Email.employee.email=\u7535\u5B50\u90AE\u4EF6\u5730\u5740\u4E0D\u5408\u6CD5
Past.employee.birthDay=\u65E5\u671F\u5FC5\u987B\u662F\u4E00\u4E2A\u8FC7\u53BB\u7684\u65F6\u95F4
typeMismatch.employee.birthDay=\u4E0D\u662F\u4E00\u4E2A\u65E5\u671F\u6709\u6548\u683C\u5F0F
② 声明国际化资源配置
<!-- 声明国际化资源文件 -->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"></property>
</bean>
14. 返回JSON
1) 处理 JSON
① 加入 jar 包:
http://wiki.fasterxml.com/JacksonDownload/ 下载地址
jackson-annotations-2.1.5.jar
jackson-core-2.1.5.jar
jackson-databind-2.1.5.jar
② 编写目标方法,使其返回 JSON 对应的对象或集合
@ResponseBody //SpringMVC对JSON的支持
@RequestMapping("/testJSON")
public Collection<Employee> testJSON(){
return employeeDao.getAll();
}
③ 增加页面代码:index.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>
<script type="text/javascript" src="scripts/jquery-1.9.1.min.js"></script>
<script type="text/javascript">
$(function(){
$("#testJSON").click(function(){
var url = this.href ;
var args = {};
$.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);
}
});
return false ;
});
});
</script>
</head>
<body>
<a href="empList">To Employee List</a>
<br><br>
<a id="testJSON" href="testJSON">testJSON</a>
</body>
</html>
④ 测试
15. HttpMessageConverter原理
1) HttpMessageConverter
① HttpMessageConverter 是 Spring3.0 新添加的一个接口,负责将请求信息转换为一个对象(类型为 T),将对象(类型为 T)输出为响应信息
② HttpMessageConverter接口定义的方法:
Boolean canRead(Class<?> clazz,MediaType mediaType): 指定转换器可以读取的对象类型,即转换器是否可将请求信息转换为 clazz 类型的对象,同时指定支持 MIME 类型(text/html,applaiction/json等)
Boolean canWrite(Class<?> clazz,MediaType mediaType):指定转换器是否可将 clazz 类型的对象写到响应流中,响应流支持的媒体类型在MediaType 中定义。
List getSupportMediaTypes():该转换器支持的媒体类型。
T read(Class<? extends T> clazz,HttpInputMessage inputMessage):将请求信息流转换为 T 类型的对象。
void write(T t,MediaType contnetType,HttpOutputMessgae outputMessage):将T类型的对象写到响应流中,同时指定相应的媒体类型为 contentType。
package org.springframework.http;
import java.io.IOException;
import java.io.InputStream;
public interface HttpInputMessage extends HttpMessage {
InputStream getBody() throws IOException;
}
package org.springframework.http;
import java.io.IOException;
import java.io.OutputStream;
public interface HttpOutputMessage extends HttpMessage {
OutputStream getBody() throws IOException;
}
③ DispatcherServlet 默认装配 RequestMappingHandlerAdapter ,
而 RequestMappingHandlerAdapter 默认装配如下 HttpMessageConverter:
④ 加入 jackson jar 包后, RequestMappingHandlerAdapter
装配的 HttpMessageConverter 如下:
默认情况下数组长度是6个;增加了jackson的包,后多个一个
MappingJackson2HttpMessageConverter
16. 使用HttpMessageConverter
使用 HttpMessageConverter<T> 将请求信息转化并绑定到处理方法的入参中或将响应结果转为对应类型的响应信息,Spring 提供了两种途径:
使用 @RequestBody / @ResponseBody 对处理方法进行标注
使用 HttpEntity<T> / ResponseEntity<T> 作为处理方法的入参或返回值
当控制器处理方法使用到 @RequestBody/@ResponseBody 或
HttpEntity/ResponseEntity 时, Spring 首先根据请求头或响应头的 Accept 属性选择匹配的 HttpMessageConverter, 进而根据参数类型或泛型类型的过滤得到匹配的 HttpMessageConverter, 若找不到可用的 HttpMessageConverter 将报错
@RequestBody 和 @ResponseBody 不需要成对出现
Content-Disposition:attachment; filename=abc.pdf
① 实验代码
<form action="testHttpMessageConverter" method="post" enctype="multipart/form-data">
文件: <input type="file" name="file"/><br><br>
描述: <input type="text" name="desc"/><br><br>
<input type="submit" value="提交"/>
</form>
@ResponseBody //@ResponseBody:是将内容或对象作为Http响应正文返回
@RequestMapping("/testHttpMessageConverter")
//@RequestBody:是将Http请求正文插入方法中,修饰目标方法的入参
public String testHttpMessageConverter(@RequestBody String body){
System.out.println("body="+body);
return "Hello," + new Date(); //不再查找跳转的页面
}
② 实验代码
/files/abc.txt 准备一个下载的文件
<a href="testResponseEntity">abc.txt</a>
@RequestMapping("testResponseEntity")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException{
ServletContext servletContext = session.getServletContext();
InputStream resourceAsStream = servletContext.getResourceAsStream("/files/abc.txt");
byte[] body = new byte[resourceAsStream.available()] ;
resourceAsStream.read(body);
MultiValueMap<String, String> headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment;filename=abc.txt");
HttpStatus statusCode = HttpStatus.OK;
ResponseEntity<byte[]> responseEntity = new ResponseEntity<byte[]>(body, headers, statusCode);
return responseEntity ;
}
③ 源码参考
• HttpHeaders
• HttpStatus