<!-- 解决问题:
1.数据类型转换
2.数据格式
3.数据校验
-->
BirthDay :<form:input path="birthDay"/>
Employee类中增加日期类型属性:
//关于类型转换
private Date birthDay ;
Spring MVC 通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是 DataBinder,运行机制如下:
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);
}
}
}
Spring 定义了 3 种类型的转换器接口,实现任意一个转换器接口都可以作为自定义转换器注册到 ConversionServiceFactoryBean 中:
Converter<S,T>:将 S 类型对象转为 T 类型对象ConverterFactory:将相同系列多个 “同质” Converter 封装在一起。如果希望将一种类型的对象转换为另一种类型及其子类的对象(例如将 String 转换为 Number 及 Number 子类(Integer、Long、Double 等)对象)可使用该转换器工厂类GenericConverter:会根据源类对象及目标类对象所在的宿主类中的上下文信息进行类型转换自定义转换器示例需求:字符串转换为对象。
步骤:
定义页面<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 的上下文中
查看,框架出厂设置,与目前将我们的自定义类型转换器加入出厂设置中。
当配置了<mvc:annotation-driven/>后,会自动加载org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
和org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
<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数据验证,也需要配置 关于 <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/>配置了
/**
由 @InitBinder 标识的方法,可以对 WebDataBinder 对象进行初始化。
WebDataBinder 是 DataBinder 的子类,用于完成由表单字段到 JavaBean 属性的绑定
@InitBinder方法不能有返回值,它必须声明为void。
@InitBinder方法的参数通常是 WebDataBinder
*/
@InitBinder
public void initBinder(WebDataBinder dataBinder){
dataBinder.setDisallowedFields("lastName");
}
FormattingConversionService 实现类,该实现类扩展了 GenericConversionService,因此它既具有类型转换的功能,又具有格式化的功能
FormattingConversionService 拥有一个 FormattingConversionServiceFactroyBean 工厂类,后者用于在 Spring 上下文中构造前者,FormattingConversionServiceFactroyBean 内部已经注册了 : NumberFormatAnnotationFormatterFactroy:支持对数字类型的属性使用@NumberFormat 注解
JodaDateTimeFormatAnnotationFormatterFactroy:支持对日期类型的属性使用@DateTimeFormat 注解
装配了 FormattingConversionServiceFactroyBean 后,就可以在 Spring MVC 入参绑定及模型数据输出时使用注解驱动了。 <mvc:annotation-driven/> 默认创建的 ConversionService 实例即为DefaultFormattingConversionService
<!-- 声明类型转换器服务 -->
<!-- <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>
<!-- 解决问题:
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 />
Salary : <form:input path="salary"/>
@NumberFormat(pattern="#,###,###.#")
private double salary ;
(后台获取错误消息,并打印)
//添加员工
@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"
①使用JSR 303验证标准
②加入hibernate validator验证框架
③在SpringMVC配置文件中增加<mvc:annotation-driven/>
④需要在bean的属性上增加对应验证的注解
⑤在目标方法bean类型的前面增加@Valid注解
验证出错后,跳转到哪个页面错误消息,如何显示,如何国际化JSR 303是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 中 .
JSR 303 (Java Specification Requests意思是Java 规范提案)通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证
Hibernate Validator 扩展注解Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解
hibernate-validator-5.0.0.CR2\dist
hibernate-validator-5.0.0.CR2.jarhibernate-validator-annotation-processor-5.0.0.CR2.jarhibernate-validator-5.0.0.CR2\dist\lib\required (EL就不需要加了)classmate-0.8.0.jarjboss-logging-3.1.1.GA.jarvalidation-api-1.1.0.CR1.jar在验证属性上增加验证注解public class Employee {
private Integer id;
@NotEmpty
private String lastName;
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 ErrorsSpring MVC 是通过对处理方法签名的规约来保存校验结果的:前一个表单/命令对象的校验结果保存到随后的入参中,这个保存校验结果的入参必须是 BindingResult 或 Errors 类型,这两个类都位于 org.springframework.validation 包中
需校验的 Bean 对象和其绑定结果对象或错误对象是成对出现的,它们之间不允许声明其他的入参
Errors 接口提供了获取错误信息的方法,如 getErrorCount() 或 getFieldErrors(String field)
BindingResult 扩展了 Errors 接口
<!-- 显示所有的错误消息 -->
<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>
Pattern.user.password
Pattern.password
Pattern.java.lang.String
Pattern
当使用 Spring MVC 标签显示错误消息时, Spring MVC 会查看 WEB 上下文是否装配了对应的国际化消息,如果没有,则显示默认的错误消息,否则使用国际化消息。若数据类型转换或数据格式转换时发生错误,或该有的参数不存在,或调用处理方法时发生错误,都会在隐含模型中创建错误消息。其错误代码前缀说明如下:required:必要的参数不存在。如 @RequiredParam(“param1”) 标注了一个入参,但是该参数不存在
typeMismatch:在数据绑定时,发生数据类型不匹配的问题
methodInvocation:Spring MVC 在调用处理方法时发生了错误
注册国际化资源文件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>
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>
测试Boolean canRead(Class<?> clazz,MediaType mediaType): 指定转换器可以读取的对象类型,即转换器是否可将请求信息转换为 clazz 类型的对象,同时指定支持 MIME 类型(text/html,applaiction/json等)
Boolean canWrite(Class<?> clazz,MediaType mediaType):指定转换器是否可将 clazz 类型的对象写到响应流中,响应流支持的媒体类型在MediaType 中定义。
List<MediaType> 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<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
