数据类型的转换、格式化、校验

1、数据绑定流程

1. Spring MVC 主框架将 ServletRequest 对象及目标方
法的入参实例传递给 WebDataBinderFactory 实例,以创
建 DataBinder 实例对象
2. DataBinder 调用装配在 Spring MVC 上下文中的 
ConversionService 组件进行数据类型转换、数据格式
化工作。将 Servlet 中的请求信息填充到入参对象中
3. 调用 Validator 组件对已经绑定了请求消息的入参对象
进行数据合法性校验,并最终生成数据绑定结果
BindingData 对象
4. Spring MVC 抽取 BindingResult 中的入参对象和校验
错误对象,将它们赋给处理方法的响应入参

 

 

 

自定义类型转换器(了解)

前台页面

<form action="testConversionServiceConverer" method="post">
Employee:<input type="text" name="employee" />
<input type="submit" value="Submit" />
</form>

转换器类(要实现converter接口)

package com.oracle.converters;

import org.springframework.core.convert.converter.Converter;
import org.springframework.stereotype.Component;

import com.oracle.entities.Department;
import com.oracle.entities.Employee;

@Component
public class MyConverter implements Converter<String, Employee> {

@Override
public Employee convert(String source) {
if(source != null){
String[] vals = source.split("-");
if(vals != null&&vals.length==3){
String lastName = vals[0];
Integer gender = Integer.parseInt(vals[1]);
Department department = new Department();
department.setId(Integer.parseInt(vals[2]));
Employee employee = new Employee(null, lastName, gender, department);
return employee;
}
}
return null;
}

}

spring mvc配置文件中配置

<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
<!-- 配置自定义拦截器 -->
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<ref bean="myConverter"/>
</set>
</property>
</bean>

在控制类中使用

@RequestMapping("testConversionServiceConverer")
public String test(@RequestParam("employee") Employee employee){
employeeDao.save(employee);
return "redirect:/springmvc/listall";
}

关于 mvc:annotation-driven

<mvc:annotation-driven /> 会自动注
册RequestMappingHandlerMapping
、RequestMappingHandlerAdapter 与
ExceptionHandlerExceptionResolver 三个bean。
还将提供以下支持:
支持使用 ConversionService 实例对表单参数进行类型转换 –
支持使用 @NumberFormat annotation、@DateTimeFormat –
注解完成数据类型的格式化
支持使用 @Valid 注解对 JavaBean 实例进行 JSR 303 验证 –
支持使用 @RequestBody 和 @ResponseBody 注解

InitBinder注解

注解的作用:

由 @InitBinder 标识的方法,可以对 WebDataBinder 对
象进行初始化

WebDataBinder的作用 

①WebDataBinder 是 DataBinder 的子类,用
于完成由表单字段到 JavaBean 属性的绑定

②完成数据类型的转换,数据校验

③如果在数据转换和校验的过程中,出现错误信息,通过返回BindingResult,显示错误信息

实例:

@InitBinder
public void initBinder(WebDataBinder binder){
binder.setDisallowedFields("lastName");
}

数据格式化

操作步骤

①在spring mvc 的配置文件中配置<mvc:annotation-driven></mvc:annotation-driven>

②在对应的实体类的属性上加上注解,例如

@DateTimeFormat(pattern="yyyy-MM-dd")
private Date birth;
@NumberFormat(pattern="#,###,#.#")
private float salary;

格式化原理

对属性对象的输入/输出进行格式化,从其本质上讲依然
属于 “类型转换” 的范畴。
Spring 在格式化模块中定义了一个实现
ConversionService 接口的
FormattingConversionService 实现类,该实现类扩展
了 GenericConversionService,因此它既具有类型转换的
功能,又具有格式化的功能
FormattingConversionService 拥有一个
FormattingConversionServiceFactroyBean 工厂类,
后者用于在 Spring 上下文中构造前者

FormattingConversionServiceFactroyBean  内部已经注册了 :
NumberFormatAnnotationFormatterFactroy:支持对数字类型的属性 –
使用 @NumberFormat 注解
JodaDateTimeFormatAnnotationFormatterFactroy:支持对日期– 类型
的属性使用 @DateTimeFormat 注解
装配了 FormattingConversionServiceFactroyBean 后,就可
以在 Spring MVC 入参绑定及模型数据输出时使用注解驱动
了。<mvc:annotation-driven/> 默认创建的
ConversionService 实例即为
FormattingConversionServiceFactroyBean

BindingResult 

BindingResult中存放了处理的异常信息,可以通过入参是获取到BindingResult,进而获取到异常信息

@RequestMapping(value="/add",method=RequestMethod.POST)
public String save(Employee employee,BindingResult result){
System.out.println(employee);
if(result.getErrorCount()>0){
for(FieldError error:result.getFieldErrors()){
System.out.println(error.getField() + ":" + error.getDefaultMessage());
}
}
employeeDao.save(employee);
return "redirect:/springmvc/listall";
}

数据校验

①. 使用 JSR 303 验证标准

JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,
它已经包含在 JavaEE 6.0 中


②. 加入 hibernate validator 验证框架的 jar 包

在官网下载hibernate validator的jar包

加入以下六个jar包

classmate-1.1.0.jar
hibernate-validator-5.2.1.Final.jar
hibernate-validator-annotation-processor-5.2.1.Final.jar
hibernate-validator-cdi-5.2.1.Final.jar
jboss-logging-3.2.1.Final.jar
validation-api-1.1.0.Final.jar


③. 在 SpringMVC 配置文件中添加 <mvc:annotation-driven />
④. 需要在 bean 的属性上添加对应的注解

@NotEmpty
private String lastName;

@Email
private String email;

⑤. 在目标方法 bean 类型的前面添加 @Valid 注解

通过在处理方法的入参上标
注 @valid 注解即可让 Spring MVC 在完成数据绑定后执行
数据校验的工作


2). 验证出错转向到哪一个页面 ?
注意: 需校验的 Bean 对象和其绑定结果对象或错误对象时成对出现的,它们之间不允许声明其他的入参

 

错误消息的显示和国际化

jsp页面显示

显示全部错误消息

<form:errors path="*"></form:errors>

显示在对应的记录位置

LastName:<form:input path="lastName"/><br>
<form:errors path="lastName"></form:errors>

 

国际化错误消息

当使用 Spring MVC 标签显示错误消息时, Spring MVC 会查看
WEB 上下文是否装配了对应的国际化消息,如果没有,则显示默认
的错误消息,否则使用国际化消息。

①i18n.properties

当一个属性校验失败后,校验框架会为该属性生成 4 个消 
息代码,这些代码以校验注解类名为前缀,结合
modleAttribute、属性名及属性类型名生成多个对应的消
息代码

例如

NotEmpty.employee.lastName=\u540D\u5B57\u4E0D\u80FD\u4E3A\u7A7A
Past.employee.birth = \u751F\u65E5\u5FC5\u987B\u662F\u4E00\u4E2A\u8FC7\u53BB\u7684\u65F6\u95F4

②配置国际化消息

<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="i18n"></property>
</bean>

③补充

若数据类型转换或数据格式转换时发生错误,或该有的参
数不存在,或调用处理方法时发生错误,都会在隐含模型
中创建错误消息。其错误代码前缀说明如下:
required:必要的参数不存在。如 @RequiredParam(“param1”) –
标注了一个入参,但是该参数不存在
typeMismatch:在数据绑定时,发生数据类型不匹配的问题 –
methodInvocation:Spring MVC 在调用处理方法时发生了错误

ajax

①加入jackson的jar包

jackson-annotations-2.4.0.jar
jackson-core-2.4.5.jar
jackson-databind-2.4.6.1.jar

②前台发送ajax请求

$(function(){
$("#id").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;
});
})

③后台通过配置@ResponseBody和方法的返回值类型返回json数据

@ResponseBody
@RequestMapping("ajax")
public Collection<Employee> testJson(){
return employeeDao.getEmployees();
}

实现原理

通过HttpMessageConverter<T>

上传下载

①添加jar包

commons-fileupload-1.2.1.jar
commons-io-2.0.jar

②配置springmvc 的配置文件

<!-- 配置 MultipartResolver -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"></property>
<property name="maxUploadSize" value="1024000"></property>
</bean>

上传的页面

<form action="testFileUpload" method="POST" enctype="multipart/form-data">
File: <input type="file" name="file"/>
Desc: <input type="text" name="desc"/>
<input type="submit" value="Submit"/>
</form>

后台处理

@RequestMapping("/testFileUpload")
public String testFileUpload(@RequestParam("desc") String desc,
@RequestParam("file") MultipartFile file) throws IOException{
System.out.println("desc: " + desc);
System.out.println("OriginalFilename: " + file.getOriginalFilename());
System.out.println("InputStream: " + file.getInputStream());
return "success";
}

 

下载的页面

<a href="testResponseEntity">Test ResponseEntity</a>

后台处理

@RequestMapping("/testResponseEntity")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException{
byte [] body = null;
ServletContext servletContext = session.getServletContext();
InputStream in = servletContext.getResourceAsStream("/files/abc.txt");
body = new byte[in.available()];
in.read(body);

HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment;filename=abc.txt");

HttpStatus statusCode = HttpStatus.OK;

ResponseEntity<byte[]> response = new ResponseEntity<byte[]>(body, headers, statusCode);
return response;
}

 

国际化

关于国际化:
1. 在页面上能够根据浏览器语言设置的情况对文本(不是内容), 时间, 数值进行本地化处理
2. 可以在 bean 中获取国际化资源文件 Locale 对应的消息
3. 可以通过超链接切换 Locale, 而不再依赖于浏览器的语言设置情况

解决:
1. 使用 JSTL 的 fmt 标签
2. 在 bean 中注入 ResourceBundleMessageSource 的示例, 使用其对应的 getMessage 方法即可
3. 配置 LocalResolver 和 LocaleChangeInterceptor

拦截器

①实现HandlerInterceptor接口

package com.atguigu.springmvc.interceptors;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class FirstInterceptor implements HandlerInterceptor{

/**
* 该方法在目标方法之前被调用.
* 若返回值为 true, 则继续调用后续的拦截器和目标方法.
* 若返回值为 false, 则不会再调用后续的拦截器和目标方法.
*
* 可以考虑做权限. 日志, 事务等.
*/
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
System.out.println("[FirstInterceptor] preHandle");
return true;
}

/**
* 调用目标方法之后, 但渲染视图之前.
* 可以对请求域中的属性或视图做出修改.
*/
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("[FirstInterceptor] postHandle");
}

/**
* 渲染视图之后被调用. 释放资源
*/
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("[FirstInterceptor] afterCompletion");
}

}

②配置文件中配置

<mvc:interceptors>
  <!-- 配置自定义的拦截器 -->
  <bean class="com.atguigu.springmvc.interceptors.FirstInterceptor"></bean>
</mvc:interceptors>

拦截器补充

<mvc:interceptors>
<!-- 配置自定义的拦截器 -->

  <!-- <mvc:mapping path="/emps"/>配置拦截器作用的路径,<mvc:exclude-mapping path="/emps"/>配置拦截器不作用的路径 -->
  <mvc:interceptor>
    <mvc:mapping path="/emps"/>
    <bean class="com.atguigu.springmvc.interceptors.SecondInterceptor"></bean>
  </mvc:interceptor>

</mvc:interceptors>

多个拦截器的执行顺序

preHandle正序

postHandle和afterCompletion倒序

异常处理

Spring MVC 通过 HandlerExceptionResolver 处理程序
的异常,包括 Handler 映射、数据绑定以及目标方法执行
时发生的异常。

ExceptionHandlerExceptionResolver

主要处理 Handler 中用 @ExceptionHandler 注解定义的
方法。用ModelAndView返回异常信息

/**
* 1. 在 @ExceptionHandler 方法的入参中可以加入 Exception 类型的参数, 该参数即对应发生的异常对象
* 2. @ExceptionHandler 方法的入参中不能传入 Map. 若希望把异常信息传导页面上, 需要使用 ModelAndView 作为返回值
* 3. @ExceptionHandler 方法标记的异常有优先级的问题. 越接近发生的异常的优先级越高
* 4. @ControllerAdvice: 如果在当前 Handler 中找不到 @ExceptionHandler 方法来出来当前方法出现的异常,
* 则将去 @ControllerAdvice 标记的类中查找 @ExceptionHandler 标记的方法来处理异常.
*/

@ExceptionHandler({ArithmeticException.class})
public ModelAndView handleArithmeticException(Exception ex){
System.out.println("----> 出异常了: " + ex);
ModelAndView mv = new ModelAndView("error");
mv.addObject("exception", ex);
return mv;
}

 

去 @ControllerAdvice 标记的类中查找 @ExceptionHandler 标记的方法来处理异常. 

package com.atguigu.springmvc.test;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;

@ControllerAdvice
public class SpringMVCTestExceptionHandler {

@ExceptionHandler({ArithmeticException.class})
public ModelAndView handleArithmeticException(Exception ex){
System.out.println("----> 出异常了: " + ex);
ModelAndView mv = new ModelAndView("error");
mv.addObject("exception", ex);
return mv;
}

}

 

ResponseStatusExceptionResolver

自定义一个返回的页面

可以作用在类上,也可以是方法上

①注册

@ResponseStatus(value=HttpStatus.FORBIDDEN, reason="用户名和密码不匹配!")
public class UserNameNotMatchPasswordException extends RuntimeException{

/**
*
*/
private static final long serialVersionUID = 1L;


}

②使用

@RequestMapping("/testResponseStatusExceptionResolver")
public String testResponseStatusExceptionResolver(@RequestParam("i") int i){
if(i == 13){
throw new UserNameNotMatchPasswordException();
}
System.out.println("testResponseStatusExceptionResolver...");

return "success";
}

也可以直接放在方法上,当调用此方法时,返回异常的页面

@ResponseStatus(reason="测试",value=HttpStatus.NOT_FOUND)
@RequestMapping("/testResponseStatusExceptionResolver")
public String testResponseStatusExceptionResolver(@RequestParam("i") int i){
if(i == 13){
throw new UserNameNotMatchPasswordException();
}
System.out.println("testResponseStatusExceptionResolver...");

return "success";
}

DefaultHandlerExceptionResolver

对一些特殊的异常进行处理,比
如NoSuchRequestHandlingMethodException、HttpReques
tMethodNotSupportedException、HttpMediaTypeNotSuppo
rtedException、HttpMediaTypeNotAcceptableException
等。

SimpleMappingExceptionResolver

如果希望对所有异常进行统一处理,可以使用 
SimpleMappingExceptionResolver,它将异常类名映射为
视图名,即发生异常时使用对应的视图报告异常

<!-- 配置使用 SimpleMappingExceptionResolver 来映射异常 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionAttribute" value="ex"></property>
<property name="exceptionMappings">
<props>
<prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>
</props>
</property>
</bean>