发现问题?
- 此时有这么一个表单页面
<body>
<form:form action="${pageContext.request.contextPath}/emp" method="POST" modelAttribute="employee">
雇员名称:<form:input path="lastName"/><br/>
雇员邮箱<form:input path="email"/><br/>
<%
Map<String, String> genders = new HashMap<>();
genders.put("0", "女");
genders.put("1", "男");
request.setAttribute("genders", genders);
%>
性别:<form:radiobuttons path="gender" items="${genders}"/><br/>
部门:<form:select path="department.id" items="${departments}" itemLabel="departmentName" itemValue="id"></form:select>
<br/>
<input type="submit" value="提交"/>
</form:form>
</body>
- 这个表单对应的实体类对象为:
package mao.shu.springmvc.crud.vo;
public class Employee {
private Integer id;
private String lastName;
private String email;
private Integer gender;//1表示男,0表示女
private Department department;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
}
- 此时思考一个问题,在表单中填写数据,是如何转换为实体类中对应的参数?
- 表单中填写的数据都为String类型,而实体类中的对应的数据可能是其他数据类型,例如:Date,Integer,Double
- 那么SpringMVC是如何将String类型的数据自动转换为正确的数据类型?
- 在进行数据填写的时候,往往会存在一些逻辑上的限制,例如一个人的年龄一般不会太高(例如:2000岁),也不应该是负数等等
- 那么SpringMVC中要如何进行数据的判断?
数据绑定流程
- SpringMVc数据转换流程
- 调用binderRequestParames()方法进行数据转换和数据验证操作
- 调用conversionService进行数据转换和格式化
- 调用validates()进行数据验证处理
自定义类型转换器
- SpringMVC提供的转换器
- SpringMVC提供了大量的转换器,一般情况下足够开发使用,如果需要特殊的数据转换,可以自定义转换器
- 自定义转换器需要实现SpringMVC的接口
示例:定义一个自定义类型转换器
- 定义EmployeeConverter
- 这个转换器使用@Component注解交由SpringMVC的IOC容器管理
- 约定传入的字符串组成为:“雇员名称”-“雇员邮编”-“性别标识符数据”-“部门编号”
package mao.shu.springmvc.converter;
import mao.shu.springmvc.crud.vo.Department;
import mao.shu.springmvc.crud.vo.Employee;
import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;
@Component
public class EmployeeConverter implements Converter<String, Employee> {
@Override
public Employee convert(String s) {
//约定传入的字符串组成为:"雇员名称"-"雇员邮编"-"性别标识符数据"-"部门编号"
String[] temp = s.split("-");
if (temp != null && temp.length == 4) {
Employee employee = new Employee();
employee.setLastName(temp[0]);
employee.setEmail(temp[1]);
employee.setGender(Integer.parseInt(temp[2]));
Department dept = new Department();
dept.setId(Integer.parseInt(temp[3]));
employee.setDepartment(dept);
System.out.println(s+"--converter--"+employee);
return employee;
}
return null;
}
}
- 编写SpringMVC的配置文件
<!--配置SpringMVC的数据转换器-->
<bean id="conversionServiceFactoryBean" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<!--添加自定义的转换器-->
<ref bean="employeeConverter"/>
</set>
</property>
</bean>
<!--配置conversion-service属性 指向SpringMVC的数据转换器-->
<mvc:annotation-driven conversion-service="conversionServiceFactoryBean"></mvc:annotation-driven>
- 编写一个表单进行Employe类型的数据添加操作
<form action="testConverter" method="post">
添加的雇员信息格式为:"雇员名称"-"雇员邮编"-"性别标识符数据"-"部门编号"<br/>
添加雇员信息:<input type="text" name="employee"/>
<input type="submit" value="提交"/>
</form>
- 在控制器中编写请求映射方法
@RequestMapping(value="/testConverter",method=RequestMethod.POST)
public String testConverter(@RequestParam("employee") Employee employee){
this.employeeDAO.add(employee);
return "redirect:empList";
}
- 测试
- 后台输出
annotation-driven配置
为什么使用annotation-driven
通过源码解析annotation-driven注解使用过程
- 在SpringMVC中不配置<mvc:default-servlet-handler>和<mvc:annotation-driven>注解
- 在实体类的Setter方法上打上断点
- 执行一个请求映射方法
-
当代码停住时,选择DespatcherServlet中的976行,查看当前类中的变量情况
-
此时DispatcherServlet中的handerAdapters对象数组中包含三个变量
- 其中AnnotationMethodHanderAdapter 就支持@RequestMapping 注解映射的路径.但是这个AnnotationMethodHanderAdapter类在SpringMVC4.0之后已经过期不建议使用
- 其中AnnotationMethodHanderAdapter 就支持@RequestMapping 注解映射的路径.但是这个AnnotationMethodHanderAdapter类在SpringMVC4.0之后已经过期不建议使用
-
启动 <mvc:default-servlet-handler/>配置的时候
- 此时handlerAdapters变量中没有AnnotationMethodHandlerAdapter对象,这样就无法使 @RequestMapping 注解配置映射路径生效,那么也就无法访问页面
- 当同时启动<mvc:default-servlet-handler>和<mvc:annotation-driven>的时候
- 发现使用了一个新的对象:RequestMappingHandlerAdapter
- 此时的映射请求可以被正确处理
@initBinder 注解
@initBinder注解的作用
- 通过使用InitBinder注解定义的方法,可以用于控制DataBinder对Web数据到JavaBean数据的转换操作控制.
- 例如:一个雇员表单提交之后转换为一个Employee类实例化对象,可以通过使用@InitBinder注解控制Employee类中的哪个属性不进行赋值
- 示例:设置Employee类中的lastName属性不进行赋值操作
@InitBinder
public void setLastName(WebDataBinder webDataBinder){
//不设置lastName属性
webDataBinder.setDisallowedFields("lastName");
}
- 在执行添加操作之后,lastName属性对应的字段将不会被赋值
数据格式化
- 示例:SpringMVC的配置文件
<mvc:annotation-driven ></mvc:annotation-driven>
-
- 示例:进行日期转换
- 在Employee类中添加一个birthday 字段描述雇员的生日
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date birthday;
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
- 在表单中添加一个日期填写项
雇员生日:<form:input path="birthday" />
<br/>
- 在添加的方法中输出雇员信息
@RequestMapping(value = "/emp", method = RequestMethod.POST)
public String save(Employee vo) {
System.out.println("添加的雇员:"+vo);
this.employeeDAO.add(vo);
return "redirect:/empList";
}
- 测试
- 后台的得到的数据
- 示例:进行数值格式化
- 实体类
// 将存款字符串格式化的数值:[亿][千万][百万],[十万][万][千],[百][十][元].[角][分]
@NumberFormat(pattern="###,###,###.##")
private Integer salary;
public Integer getSalary() {
return salary;
}
public void setSalary(Integer salary) {
this.salary = salary;
}
- 控制层
@RequestMapping(value = "/emp", method = RequestMethod.POST)
public String save(Employee vo) {
System.out.println("添加的雇员:"+vo);
System.out.println("雇员工资"+vo.getSalary());
this.employeeDAO.add(vo);
return "redirect:/empList";
}
- 表单页面
雇员工资<form:input path="salary"/><br/>
- 测试
- 后台数据
- 如果希望使用SpringMVC提供的转换器的同时也是用自定义的转换器,那么可以在SpringMVC配置文件中配置FormattingConversionServiceFactoryBean 的Bean
<!--配置SpringMVC的数据转换器-->
<bean id="conversionServiceFactoryBean" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<!-- 添加自定义的转换器-->
<ref bean="employeeConverter"/>
</set>
</property>
</bean>
<!--配置conversion-service属性 指向SpringMVC的数据转换器-->
<mvc:annotation-driven conversion-service="conversionServiceFactoryBean"></mvc:annotation-driven>
数据的校验
如何进行数据验证?
- 使用JSR 303 进行数据验证
示例:进行javaBean的参数验证
- 在SpringMVC的配置文件中配置"<mvc:annotation-driven>"标签
- 加入<mvc:annotation-driven>"标签的时候默认会加载LocalValidatorFactoryBean
<mvc:annotation-driven ></mvc:annotation-driven>
- 加入hibernate validate 验证框架的jar包
3. 在java类参数中加入注解
@Email
private String email;
@Past
private java.util.Date birthday;
@Min(3000)
private Double salary;
- 在目标方法上使用 @Valid 注解修饰要验证的入参,并且添加BindingResult 参数接收验证结果
@RequestMapping(value = "/emp", method = RequestMethod.POST)
public String save(@Valid Employee vo, BindingResult bandingResult) {
//如果有错误
if(bandingResult.getErrorCount() > 0){
for (FieldError error:bandingResult.getFieldErrors()){
System.out.println("参数名称: "+error.getField()+"错误信息 "+error.getDefaultMessage());
}
}
this.employeeDAO.add(vo);
return "redirect:/empList";
}
- 测试:填写错误的数据
- 后台输出
出现错误返回指定页面
- 使用Map集合保存数据,SpringMVC会自动进行表单的回显操作
@RequestMapping(value = "/emp", method = RequestMethod.POST)
public String save(@Valid Employee vo, BindingResult bandingResult,Map<String,Object> map) {
//如果有错误
if(bandingResult.hasErrors()){
for (FieldError error:bandingResult.getFieldErrors()){
System.out.println("参数名称: "+error.getField()+"错误信息 "+error.getDefaultMessage());
}
//如果出现错误,将错误的数据保存到map集合中
map.put("employee",vo);
map.put("departments", this.departmentDAO.getAll());
return "emp_input";
}
this.employeeDAO.add(vo);
return "redirect:/empList";
}
- 输入错误后,自动跳转到目标页
页面显示错误信息
- SrpingMVC会将数据的验证结果保存在页面中的request属性中,可以使用<form:errors>标签显示错误信息
-
如果使用 <form:errors path="*">,path属性值取"*"的情况下,默认是显示所有的错误信息
-
如果要显示单个的错误信息,则path属性的取值就是实体类中的属性名称
-
示例:显示错误提示信息
雇员邮箱<form:input path="email"/>
<form:errors path="email"/>
<br/>
雇员生日<form:input path="birthday"/>
<form:errors path="birthday" />
<br/>
雇员工资<form:input path="salary" />
<form:errors path="salary"/>
<br/>
- 测试
- 显示信息
定制提示信息
- 要定制提示信息,需要编写properties文件,文件中key的值的格式为:
"校验注解名"."属性类名"."属性名"="提示信息"
- 示例:实现错误信息国际化
- 实体类中使用JSR 303 注解验证的属性
@NotEmpty
private String lastName;
@Email
private String email;
@NotEmpty
private Integer gender;//1表示男,0表示女
@Past
private java.util.Date birthday;
@Min(3000)
private Double salary;
- 定义i18n.properties文件
NotEmpty.employee.lastName=雇员姓名不能够为空
Email.employee.email=请输入正确的电子邮箱
Past.employee.birthday=不能够输入未来的日期时间
Min.employee.salary=雇员工资最小为3000
typeMismatch.employee.birthday=请输入正确的时间格式,例如"2018-10-16"
- 在SpringMvc的配置文件中配置国际化资源文件
<!--配置国际化资源文件-->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"/>
</bean>
- 测试
处理JSON
- 注意:不同版本的SpringMVC于jacksonjar包可能会有冲突
- 本次使用的是SpringMVC-4.3.1
- jackson.2.9.7
- 示例:返回json数据
@ResponseBody
@RequestMapping("/testJSON")
public Collection<Employee> testJSON(){
return this.employeeDAO.getAll();
}
HttpMessageConverter原理
- HttpMessageConverter工作原理
- HttpInputMessage接口中定义的getBody()方法,用于将请求信息转换为InputStream对象
- HttpOutputMessage接口中的getBody()方法负责将回应内容转换为OutputStream对象
- HttpMessageConverter接口的默认实现子类
- SpringMVC默认装配的实现子类有6个
- 当我将jackson的jar包导入到项目中时,会自动增加MappingJackson2HttpMessageConverter
使用HttpMessageConverter
示例:使用"@RequestBody"和"@ResponseBody"注解
/**
* 使用ResponseBody注解,将方法的返回值转换为普通的String类型
* 返回的字符串将会直接打印在客户端页面上
*
* 使用@RequestBody修饰 file 入参,将会把请求的内容转换为 String类型最为方法的参数
* @param file
* @return
*/
@ResponseBody
@RequestMapping("/testMessageConverter")
public String testMessageConverter(@RequestBody String file){
System.out.println("上传文件: "+file);
return "上传成功 "+new Date();
}
- 测试请求:
- 后台输出
- 回应内容
HttpEntity和ResponseEntity使用示例
- 使用HttpEntity和ResponseEntity最为一个对象为参数或者返回值可以接收或返回更多的信息.
- 示例:返回一个文件信息
- 返回的文件
@RequestMapping("/testResponseEntity")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
//将一个静态文件转换为一个InputStream对象
InputStream guidaoImg =session.getServletContext().getResourceAsStream("/img/guidao.jpg");
//创建InputStream对象长度的 byte[] 字节数组
byte[] guidaoByte = new byte[guidaoImg.available()];
//将InputStream 对象中的内容保存到 字节数组中
guidaoImg.read(guidaoByte);
//设置请求头
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Content-Disposition","attachment;filename=guidao.jpg");
//HttpStatus.OK 描述的是请求的状态码 HttpStatus.OK = 200
HttpStatus httpStatus = HttpStatus.OK;
//将字节数组最为请求回应的内容
ResponseEntity<byte[]> file = new ResponseEntity<>(guidaoByte,httpHeaders,httpStatus);
return file;
}
- 测试请求:
<a href="testResponseEntity">文件下载测试</a>
- 成功下载