必定要统一各个 jar 文件的版本,否则启动服务器时会出现异常html
org.springframework.beans.factory.BeanCreationException:
java
Error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping',git
NoSuchMethodError: org.springframework.web.bind.annotation.RequestMapping.path()[Ljava/lang/String; 错误web
本文程序需用到的包有spring
Spring 会根据请求方法签名的不一样,将请求消息中的信息以必定的方式转换并绑定到请求方法的入参中。在请求消息到达真正调用处理方法的这一段时间内,SpringMVC 还完成了不少工做,包括数据转换、数据格式化及数据校验等。数据库
SpringMVC 经过反射机制对目标处理方法的签名进行分析,将请求消息绑定处处理方法的入参中。api
数据绑定的核心部件是 DataBinder,它的进行机制描述如图spring-mvc
1,2. SpringMVC 主框架将 ServletRequest 对象及目标方法的入参实例传递给 WebDataBinderFactory 实例,以建立 DataBinder 实例对象tomcat
3. DataBinder 调用装配在 SpringMVC 上下文的 ConversionService 组件进行数据类型转换、数据格式化工做,并将 Servlet 中的请求信息填充到入参对象中服务器
4. 调用 Validator 组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果 BindingResult 对象
5. SpringMVC 抽取 BindingResult 中的入参对象和校验错误对象,将它们赋给处理方法的相应入参
Java 标准的 PropertyEditor 的核心功能是将一个字符串装换成一个 Java 对象,以便根据界面的输入或配置文件中的配置字符串构造出一个 JVM 内部的 Java 对象。
但 Java 原生的 PropertEditor 存在如下不足:
1. 只能用于字符串和Java 对象的转换,不适用于任意两个 Java类型之间的转换
2. 对源数据及目标对象所在的上下文信息(如注解、所在宿主类的结构类)不敏感,在类型转换时不能利用这些上下文信息实施高级转换逻辑
鉴于此种状况,Spring 在核心模型中添加了一个通用的类型转换模块
ConversionService 是 Spring 类型转换体系的核心接口,它位于 org.springframework.core.convert 包中
能够利用 org.springframework.context.support.ConversionServiceFactoryBean 在 Spring 的上下文中定义一个 ConversionService。Spring 将自动识别出上下文中的 ConversionService,并在 Bean 属性配置及 SpringMVC 处理方法入参绑定等场合使用它进行数据格式的转换。该 FacyoryBean 建立 ConversionService 内建了不少的转换器,可完成大多数 Java 类型的转换工做。除了包括将 String 对象转换为各类基础类型的对象外,还包括 String、Number、Array、Collection、Map、Properties 及 Object 之间的转换器。
能够经过 ConversionServiceFactoryBean 的 converters 属性注册自定义的类型转换器。自定义的转换器必须实现 org.springframework.core.convert.converter 包中的转换器接口。该包中一共定义了3中类型的转换器接口,实现任意一个转换器接口均可以做为自定义转换器注册到 ConversionServiceFactoryBean 中。这3个类型的转换器接口分别为:
1. Converter<S, T>:将 S 类型的对象转为 T 类型的对象
2. ConverterFactory:将相同系列多个“同质”Converter封装在一块儿。若是但愿将一种类型的对象转换为另外一种类型及其子类的对象(例如将 String 转换为 Number 及 Number子类(Integer、Long、Double等))可使用该转换器工厂类
3. GenericConverter:会根据源类对象及目标类对象所在的宿主类中的上下文信息进行类型转换
如今咱们经过一个例子来看如何自定义转换器
假设处理方法有一个 Employee 类型的入参,咱们但愿将一个请求参数字符串直接转换为 Employee 对象,该字符串的格式为:<lastName>-<Email>
新建 javaweb 项目,导入 Spring 相关的包
新建自定义的转换器 EmployeeConerter 继承 Converter<S, T> 接口类
package com.bupt.springmvc.converter.converter; import org.springframework.core.convert.converter.Converter; import org.springframework.stereotype.Component; import com.bupt.springmvc.converter.entity.Employee; @Component public class EmployeeConverter implements Converter<String, Employee> { @Override public Employee convert(String arg0) { if(arg0 != null) { //按"-"来分割输入的字符串 String[] vals = arg0.split("-"); if(vals != null && vals.length == 2) { String lastName = vals[0]; String email = vals[1]; Employee employee = new Employee(null, lastName, email); System.out.println(arg0 + " : " + employee); return employee; } } return null; } }
新建实体类 Employee 和模拟数据库操做的 EmployeeDao 类
package com.bupt.springmvc.converter.entity; public class Employee { private Integer id; private String lastName; private String email; //生成 getter 和 setter 方法,生成带参和不带参的构造器,重写 toString() }
package com.bupt.springmvc.converter.Dao; import java.util.Collection; import java.util.HashMap; import java.util.Map; import org.springframework.stereotype.Repository; import com.bupt.springmvc.converter.entity.Employee; @Repository public class EmployeeDao { private static Map<Integer, Employee> employees = null; static { employees = new HashMap<Integer, Employee>(); employees.put(1001, new Employee(1001, "E-AA", "aa@163.com")); employees.put(1002, new Employee(1002, "E-BB", "bb@163.com")); employees.put(1003, new Employee(1003, "E-CC", "cc@163.com")); employees.put(1004, new Employee(1004, "E-DD", "dd@163.com")); employees.put(1005, new Employee(1005, "E-EE", "ee@163.com")); } private static Integer initId = 1006; public void save(Employee employee) { if (employee.getId() == null) { employee.setId(initId++); } employees.put(employee.getId(), employee); } public Collection<Employee> getAll() { return employees.values(); } public Employee get(Integer id) { return employees.get(id); } public void delete(Integer id) { employees.remove(id); } }
新建方法处理器类 ConverterHandler
package com.bupt.springmvc.converter.handler; import java.util.Map; 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.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import com.bupt.springmvc.converter.Dao.EmployeeDao; import com.bupt.springmvc.converter.entity.Employee; @Controller public class ConverterHandler { @Autowired private EmployeeDao employeeDao; @RequestMapping("/emps") public String list(Map<String, Object> map) { map.put("employees", employeeDao.getAll()); return "list"; } @RequestMapping(value="/emp", method=RequestMethod.GET) public String input(Map<String, Object> map) {
return "input"; } @RequestMapping(value="/testConversionServiceConverter", method=RequestMethod.POST) public String testConverter(@RequestParam("employee") Employee employee) { employeeDao.save(employee); return "redirect:/emps"; } }
配置 web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1"> <!-- 自定义的spring 配置文件能够放置在类路径 src下,名字在 param-value 属性中指定 --> <servlet> <servlet-name>springDispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:springmvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springDispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
类路径 src 下新建 springmvc.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!-- 配置自动扫描的包 --> <context:component-scan base-package="com.bupt.springmvc.converter"/> <!-- 配置视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"></property> <property name="suffix" value=".jsp"></property> </bean> <mvc:annotation-driven conversion-service="ConversionService"/> <!-- 配置ConversionService --> <bean id="ConversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <set> <ref bean="employeeConverter"/> </set> </property> </bean> </beans>
WEB-INF 下新建 viws 文件夹,内新建 list.jsp 和 input.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Insert title here</title>
</head>
<body>
<table border="1" cellpadding="10" cellspacing="0">
<tr>
<th>ID</th>
<th>LastName</th>
<th>Email</th>
</tr>
<c:forEach items="${requestScope.employees }" var="emp">
<tr>
<td>${emp.id }</td>
<td>${emp.lastName }</td>
<td>${emp.email }</td>
</tr>
</c:forEach>
</table>
<br><br>
<a href="emp">Add New Employee</a>
</body>
</html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@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> <title>Insert title here</title> </head> <body> <form action="testConversionServiceConverter" method="post"> Employee: <input type="text" name="employee"> <input type="submit" value="submit"> </form> </body> </html>
WebContent 下新建 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>
<title>Insert title here</title>
</head>
<body>
<a href="emps">List All Employees</a>
</body>
</html>
部署项目后启动 tomcat 访问,访问 index.jsp 页面,点击超连接,页面跳转到以下图页面
点击 Add New Employee 超连接,跳转到 input.jsp 页面,咱们在输入框输入如图所示的数据格式
提交后效果如图
由此能够看出,咱们自定义的转换器已经把咱们输入的特定规则的字符串转换成了 Employee 对象。
若是咱们在 spring 配置文件中加上 <mvc:annotation-drivern>,它会自动注册 RequestMappingHandlerMapping、RequestMappingHandlerAdapter 与 ExceptionHandlerExceptionResolver 三个 bean。
还将提供如下支持:
1. 支持使用 ConversionService 实例对表单参数进行类型转换
2. 支持使用 @NumberFormat 注解、@DataTimeFormat 注解完成数据类型的格式化
3. 支持使用 @Valid 注解对 JavaBean 实例进行 JSR 303 验证
4. 支持使用 @RequestBody 和 @ResponseBody 注解
咱们经过 Debug 来看一下为何要添加 <mvc:annotation-drivern>
咱们 Debug 的代码是上一篇博文写的 CRUD 程序,在 Employee 实体类的 setLastName() 方法内设置断点的,点击 Add New Employee 超连接,填写表单提交数据
当配置了<mvc:annotation-drivern conversion-service=" "/> 属性,即存在自定义的转换器状况下。conversionService 的值是 DefaultConversionService,它其实就是 conversion-service 属性值所指的转换器
当把 <mvc:annotation-drivern conversion-service=" "/> 属性去掉后,再进行 Debug,此时 conversionService 变成 spring内置的转换器 DefaultFormattingConversionService
当把 <mvc:annotation-drivern> 注释掉后,此时 conversionService 变为 null
SpringMVC 在支持新的转换器框架的同时,也支持 JavaBeans 的 PropertyEditor。能够在控制器中使用 @InitBinder 添加自定义的编辑器。
由 @InitBinder 标识的方法,能够对 WebDataBinder 对象进行初始化。WebDataBinder 是 DataBinder 的子类,用于完成由表单字段到 JavaBean 属性的绑定。
@InitBinder 方法不能有返回值,它必须声明为 void
@InitBinder 方法的参数一般是 WebDataBinder
如今经过代码来讲明它的一些用法
之前面添加员工信息做为例子,若是咱们但愿某个属性好比 lastName 不进行赋值时,就可使用以下代码,这就使得结果页面不会出现新增员工的 lastName 值。
@Controller public class ConverterHandler { @InitBinder public void initBinder(WebDataBinder binder) { //提交表单时不自动绑定对象中的 lastName 属性,另行处理 binder.setDisallowedFields("lastName"); } }
@InitBinder 最主要的做用仍是用来为控制器注册属性编辑器,Spring MVC 本身提供了大量的实现类,包括 CustomDateEditor、 CustomBooleanEditor、 CustomNumberEditor 等。固然,咱们也能够自定义编辑器类而不使用 Spring MVC 为咱们提供的编辑器类。
//自定义编辑器需继承 PropertiesEditorSupport public class UserEditor extends PropertiesEditorSupport { //自定义逻辑 }
这种使用 @InitBinder 注释注册的属性编辑器,只对当前 Controller 有效
@Controller public class ConverterHandler { //在控制器初始化时调用 @InitBinder public void initBinder(WebDataBinder binder) { //注册指定自定义的编辑器 binder.registerCustomEditor(User.class, new UserEditor());
//注册 SpringMVC 自带编辑器,日期实现字符串和Date类型的转换
binder.registerCustomEditor(Date.class, new CustomDateEditor(new SimpleDateFormat("yyy-MM-dd"), false)); } }
若是但愿在全局范围内使用 UserEditor 编辑器,则可实现 WebBindingInitializer 接口并在该实现类中注册 UseEditor
public class MyBindingInitializer implements WebBindingInitializer { @Override public void initBinder(WebDataBinder binder, WebRequest request) { binder.registerCustomEditor(Employee.class, new UserEditor()); } }
在 initBinder() 接口方法中注册 UserEditor 编辑器后。接下来,还须要在 Web 上下文中经过 AnnotationMethodHandlerAdapter 装配 MyBindingInitializer
配置 springmvc.xml
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter"> <property name="webBindingInitializer"> <bean class="com.bupt.springmvc.converter.converter.MyBindingInitializer"/> </property> </bean>
对于同一个类型对象来讲,若是既在 ConversionService 装配自定义的转换器,又经过 WebBindingInitializer 装配了自定义编辑器,同时还在控制中经过 @InitBinder 装配了自定义编辑器,那么 SpringMVC 将按照如下的优先顺序查找对应类型的编辑器:
1. 查询经过 @InitBinder 装配的自定义编辑器
2. 查询经过 ConversionService 装配的自定义转换器
3. 查询经过 WebBindingInitializer 装配的自定义编辑器
Spring 使用转换器进行源类型对象到目标类型对象的转换,Spring 的转换器并不提供输入输出信息格式化工做。若是须要转换的源类型数据(通常为字符串)是从客户端界面传过来的,为了方便使用者,这些数据一每每是拥有必定的格式,如日期、时间和数字等。如何从格式化的数据中获取真正的数据以完成数据绑定,并将处理完成的数据输出为格式化的数据是Spring 格式化框架要解决的问题。
Spring 在 org.springframework.format 包下定义了一个格式化框架中最重要的接口 Formatter<T> 接口。它的实现类如:DateFormatter 提供了一个用于时间对象格式化,NumberFormatter 提供了用于数字类型对象的格式化等。能够手工调用这些 Formatter 接口实现类进行对象数据输入/输出的格式化工做,这种硬编码的格式化显然不符合咱们所追求的低耦合原则。因此 Spring 为咱们提供了注解驱动的属性对象格式化功能:在 Bean 属性设置、Spring MVC 处理方法入参数据绑定、模型数据输出时自动经过注解应用格式化功能。
其实对属性对象的输入/输出进行格式化,从其本质上来说依然属于“类型转换”的范畴。
Spring 在格式化模块中定义了一个实现 ConversionService 接口的 FormattingConversionService 实现类,所以他既具备类型转换的功能,又具备格式化功能。
相对于 ConversionService 的 ConversionServiceFactoryBean 工厂类,FormattingConversionService 也拥有一个对应的 FormattingConversionServiceFactoryBean 工厂类,后者用于在 Spring 上下文构造一个 FormattingConversionService。经过这个工厂类,既可注册自定义转换器,还可注册自定义的注解驱动逻辑。
FormattingConversionServiceFactoryBean内部已经注册了:
1. NumberFormatAnnotationFormatterFactory:支持对数字类型的属性使用 @NumberFormat 注解
2. JodaDateTimeFormatAnnotationFormatterFactory:支持对日期类型的属性使用 @DateTimeFormat 注解
装配了 FormattingConversionServiceFactoryBean 后,就能够在 SpringMVC 入参及模型数据输出时使用注解驱动了。
须要注意的是 <mvc:annotation-drivern conversion-service=" "/> 默认建立的 ConversionService 实例即为 FormattingConversionServiceFactoryBean。
咱们以日期格式化 @DateTimeFormat 注解和数值格式化 @NumberFormat 注解为例来看看如何在程序中使用格式化注解
1. @DateTimeFormat 注解可对 java.util.Date、java.util.Calendar 和 java.long.Long 等时间类型进行标注:
1). pattern 属性:类型为字符串。指定解析/格式化字段数据的模式,如:“yyyy-MM-dd hh:mm:ss”
2). iso 属性:类型为 DateTimeFormat.ISO,指定解析/格式化字段数据的ISO模式,包括四种: DateTimeFormat.ISO.NONE(表示不该使用ISO格式的日期)、 DateTimeFormat.ISO.DATE(yyyy-MM-dd)、 DateTimeFormat.ISO.TIME(hh:mm:ss.SSSZ)、 DateTimeFormat.ISO.DATE_TIME(yyyy-MM-dd hh:mm:ss.SSSZ)
3.) style 属性:字符串类型。经过样式指定日期和时间的格式,由两位字符组成,第一位表示日期的格式,第二位表示时间的格式,如下为几个经常使用的可选值
S:短日期/时间的样式 M:中日期/时间的样式 L:长日期/时间的样式 F:完整日期/时间的样式 -:忽略日期或时间的样式
2. @NumberFormat 可对相似数字类型的属性进行标注,它拥有两个互斥的属性:
1). pattern:类型为 String,自定义样式,如 pattern=“###,###,#”
2). style:类型为 NumberFormat.Style。用于指定样式类型,包括三种:NumberFormat.Style.NUMBER(正常数字类型)、NumberFormat.Style.CURRENCY(货币类型)、NumberFormat.Style.PERCENT(百分数类型)
经过例子来看具体用法
在以前的 Employee 实体类中添加以下属性,并生成 getter 和 setter 方法,增长构造方法,重写 toString 方法,能够在属性上增长格式化的注解
@DateTimeFormat(pattern="yyyy-MM-dd") private Date birth; @NumberFormat(pattern="###,###.#") private Float salary;
修改 springmvc.xml
<!-- 配置自动扫描的包 --> <context:component-scan base-package="com.bupt.springmvc.converter"/> <!-- 配置视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"></property> <property name="suffix" value=".jsp"></property> </bean> <!-- 默认 ConversionService 属性值即为 FormattingConversionServiceFactoryBean 的实例 --> <mvc:annotation-driven></mvc:annotation-driven> </beans>
也能够在 ConversionService 属性中直接指明其值为 FormattingConversionServiceFactoryBean
<mvc:annotation-driven conversion-service="ConversionService"></mvc:annotation-driven> <!-- 配置ConversionService,这样写既能够添加自定义的类型转换器,又能够 spring 为咱们提供的格式化功能 --> <bean id="ConversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <ref bean="employeeConverter"/> </set> </property> </bean>
重写 input.jsp 页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@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> <title>Insert title here</title> </head> <body> <form:form action="emp" Method="POST" modelAttribute="employee"> LastName: <form:input path="lastName"/><br> Email: <form:input path="email"/><br> Birth: <form:input path="birth"/><br> Salary: <form:input path="salary"/><br> <input type="submit" value="submit"> </form:form> </body> </html>
ConverterHandler 方法类重写为
@Controller public class ConverterHandler { @Autowired private EmployeeDao employeeDao; @RequestMapping("/emps") public String list(Map<String, Object> map) { map.put("employees", employeeDao.getAll()); return "list"; } @RequestMapping(value="/emp", method=RequestMethod.GET) public String input(Map<String, Object> map) { map.put("employee", new Employee()); return "input"; } @RequestMapping(value="/emp", method=RequestMethod.POST) public String input(Employee employee) { employeeDao.save(employee); System.out.println("employee: " + employee); return "redirect:/emps"; } }
启动服务器,访问 index.jsp 页面,点击超连接,跳转页面后点击 Add New Employee 超连接,填写表单以下图
点击提交,咱们能够看到控制台输出,可以正常的格式化数据
应用程序在执行业务逻辑前,必须经过数据校验保证收到的输入数据是合法的,如表明生日的日期应该是一个过去的时间,工资的数值必须是一个正数。不少时候,一样的数据验证会出如今不一样的层中,这样会致使代码冗余,为了不这样的状况。最好的方法时将验证逻辑和相应的域模型进行绑定,将代码验证的逻辑集中管理。
JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 中。
JSR 303 经过在 Bean 属性上标注相似于 @NotNull、@Max 等标准的注解指定校验规则,并经过标准的验证接口对 Bean 进行验证。它定义了一套可标注在成员变量、属性方法上的校验注解,如表所示
注解 | 功能说明 |
@Null | 被注释的元素必须为 null |
@NotNull | 被注释的元素必须不为 null |
@AssertTrue | 被注释的元素必须为true |
@AssertFalse | 被注释的元素必须为false |
@Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMax(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMin(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Size(max, min) | 被注释的元素必须的大小必须在指定的范围内 |
@Digits(integer, fraction) | 被注释的元素的大小必须是一个数字,其值必须在可接受的范围内 |
@Past | 被注释的元素必须是一个过去的日期 |
@Future | 被注释的元素必须是一个未来的日期 |
Hibernate Validator 是 JSR 303 的一个参考实现,除支持全部标准的校验注解外,它还支持如表所示的扩展注解
注解 | 功能说明 |
被注释的元素必须是电子邮箱地址 | |
@Length | 被注释的字符串的大小必须在指定范围内 |
@NotEmpty | 被注释的字符串必须非空 |
@Range | 被注释的元素必须在合适的范围内 |
那么 SpringMVC 如何实现数据校验呢,它能够分为几步
1. 使用 JSR 303 验证标准。SpringMVC 4.x 拥有本身独立的数据校验框架,同时还支持 JSR 303 标准的校验框架。Spring 在进行数据绑定时,能够同时调用校验框架完成数据校验工做。在 SpringMVC 中,能够直接经过注解驱动的方式进行数据校验。
2. 加入 Hibernate Validator 验证框架的 jar 包
Spring 自己并无提供 JSR 303 的实现,全部必须将 JSR 303 的实现的 jar 包放到类路径下,包括:
hibernate-validator-5.0.0.CR2.jar、hibernate-validator-annotation-processor-5.0.0.CR2.jar、classmate-0.8.0.jar、validation-api-1.1.0.CR1.jar 和
jboss-logging-3.1.1.GA.jar
3. 在Spring配置文件中添加 <mvc:annotation-drivern/> 注解。
Spring 的 LocalValidatorFactoryBean 既实现了 Spring 的 Validator 接口,也实现了 JSR 303 的 Validator 接口。只要在 Spring 容器中定义一个 LocalValidatorFactoryBean,便可将其注入到须要数据校验的 Bean 中。<mvc:annotation-drivern> 会默认装配好一个 LocalValidatorFactoryBean。
4. 在 Bean 属性上添加相应的注解
@NotEmpty private String lastName; @Email private String email; @Past//表示必须是一个过去的时间 @DateTimeFormat(pattern="yyyy-MM-dd") private Date birth; @NumberFormat(pattern="###,###.#") private Float salary;
5. 在处理方法的 Bean 类型的参数前添加 @Valid 注解,同时添加校验结果的入参 BiningResult 或 Errors
经过在处理方法的入参上标注 @Valid 注解便可让 SpringMVC 在完成数据绑定后执行数据校验工做。SpringMVC 框架在将请求参数绑定到该入参对象后,就会调用验证框架根据注解声明的校验规则实施校验。
SpringMVC 是经过对处理方法签名的规约来保存校验结果的:前一个表单/命令对象的校验结果保存到随后的入参中,这个保存校验结果的入参必须是 BindingResult 或 Errors 类型,这两个类都位于 prg.springframework.validation 包中
需校验的 Bean 对象和其绑定结果对象或错误对象是成对出现的,它们之间不容许声明其它的入参
Errors 接口提供了获取错误信息的方法,如 getErrorCount() 或 getFieldErrors(String field)。BindingResult 继承了 Errors 接口
在 ConerterHandler 类中改写 input 处理方法
@RequestMapping(value="/emp", method=RequestMethod.POST) public String input(@Valid Employee employee, BindingResult result) { if(result.getErrorCount() > 0) { System.out.println("error"); for(FieldError error : result.getFieldErrors()) { System.out.println(error.getField() + ": " + error.getDefaultMessage()); }
return "input"; } employeeDao.save(employee); System.out.println("employee: " + employee); return "redirect:/emps"; }
改写 input.jsp 页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@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> <title>Insert title here</title> </head> <body> <form:form action="emp" method="POST" modelAttribute="employee"> LastName: <form:input path="lastName"/><br> Email: <form:input path="email"/><br> Birth: <form:input path="birth"/><br> Salary: <form:input path="salary"/><br> <input type="submit" value="submit"> </form:form> </body> </html>
启动服务器转到提交页面,提交如图表单
控制台输出为
除了使用 Annotation JSR-303 标准进行数据校验以外,SpringMVC 还提供基于 Validator 接口的方式进行数据校验
这种状况下咱们须要提供一个 Validator 实现类,并实现 Validator 接口的 supports() 和validate() 方法。
supports() 方法用于判断当前的 Validator 实现类是否支持校验当前须要的实体类,只有当此方法的返回值为true时,该 Validator 接口实现类中的 validate() 方法才会被调用来对当前须要验证的实体类进行校验。
public class UserValidator implements Validator { public boolean supports(Class<?> clazz) { //只支持对 User 类进行验证 return User.class.equals(clazz); } public void validate(Object obj, Errors errors) { //校验 username 和 password 不为空的状况 ValidationUtils.rejectIfEmpty(errors, "username", null, "Username is empty."); User user = (User) obj; if(null == user.getPassword() || "".equals(user.getPassword())) errors.rejectValue("password", null, "Password is empty."); } }
虽然对 User 类进行校验的 UserValidator 定义好了,可是这个校验类还不能对 User 对象进行校验。由于咱们尚未告诉 SpringMVC 应该使用 UserValidator 来对 User 进行校验。咱们还须要使用 DataBinder 来设定 当前 Controller 须要使用的 Validator。
@Controller public class UserController { @InitBinder public void initBinder(DataBinder binder) { //设置当前 Controller 须要使用 UserValidator binder.setValidator(new UserValidator()); } @RequestMapping("login") //须要添加 @Valid 注解告诉 Spring 须要校验的参数 public String login(@Valid User user, BindingResult result) { if(result.hasErrors()) return "redirect:user/login"; return "redirect:/"; } }
上面定义的 Validator 只对当前的 Controller 有效,若是但愿定义一个全局的 Validator 多全部 Controller 都起做用的话,咱们能够经过 WebBindingInitializer 的 initBinder 方法来设定。另外,还能够在 SpringMVC 的配置文件中经过 <mvc:anntation> 的 validator 属性指定全局的 Validator。
public class UserBindingInitializer implements WebBindingInitializer { @Override public void initBinder(WebDataBinder binder, WebRequest request) {
//设置全局的 Validator binder.setValidator(new UserValidator()); } }
<!-- 配置时指定全局的 Validator --!>
<mvc:annotation-drivern validator="userValidator"/> <bean id="userValidator" class="com.xxx.UserValidator"/>
在前面的 UserController 中,咱们使用了 @Valid 注解来告诉 SpringMVC 咱们须要校验的参数是 user。这个注解是定义在 JSR-303 标准中的,这里使用的是 hibernate validation 对它的实现。固然,Spring 中也定义了一个注解 @Validated 它与 @Valid 实现的功能同样。可是 @Validated 为咱们带来一种叫作分组验证的校验机制,而 @Valid 则不具有这种功能。
假设咱们想在新增的状况下验证 id ,而修改的状况验证 name 和 password,这种状况下就须要分组进行校验了。
首先须要定义分组接口,分组接口就是两个普通的接口,用于标识,相似于 java.io.Serializable
public interface First{ } public interface Second{ }
接下来使用定义的接口标识实体属性
public class User implements Serializable { @NotNull(message="{user.id.null}", groups={First.class}) private Long id; @Length(min=5, max=20, message="{user.name.length.illegal}", group={Second.class}) private String name; @NotNull(message="{user.password.null}", groups={First.class, Second.class}) private String password; }
编写 Controller
@Controller public class UserController {
@RequestMapping("/save") public String save(@Validated({Second.class}) User user, BindingResult result) { if(result.hasErrors()) { return "error"; } return "success"; } }
经过 @Validated 注解标识要验证的分组,若是要验证两个的话,能够这样 @Validated({First.class, Second.class})
若是咱们想先验证一个信息,若是不经过在验证另外一个时,可使用 @GroupSequence 指定分组验证顺序
@GroupSequence({First.class, Second.class, User.class}) public class User implements Serializable { @NotNull(message="{user.id.null}", groups={First.class}) private Long id; @Length(min=5, max=20, message="{user.name.length.illegal}", group={Second.class}) private String name; @NotNull(message="{user.password.null}", groups={First.class, Second.class}) private String password; }
经过 @GroupSequence 指定验证顺序:先校验 First 分组,若是有误当即返回而不会校验 Second 分组,接着若是 First 分组验证经过了,那么才去验证 Second 分组,最后指定 User.class 表示没有分组的在最后校验。
由上面的例子咱们能够看到错误信息是会显示出来的,但它是在控制台中显示,咱们但愿的是它在页面上显示,如何作呢?
SpringMVC 除了会将表单/命令对象的校验结果保存到对应的 BindingResult 或 Errors 对象中外,还会将全部校验结果保存到“隐含模型”。
即便处理方法的签名中没有对应于表单/命令对象的结果入参,校验结果也保存在“隐含对象”中。
隐含模型中的全部数据最终将经过 HttpServletRequest 的属性列表暴露给 JSP 视图对象,所以在 JSP 中能够获取错误信息。
在 JSP 页面上可经过 <form:errors/> 显示错误信息,能够经过 path 属性值来指定显示哪部分的错误信息。
1. path="*":显示所有的错误信息
2. path="username":只显示名为 username 的表单项错误
改写 input.jsp 页面
<form:form action="emp" method="POST" modelAttribute="employee"> <form:errors path="*"/> <br><br> LastName: <form:input path="lastName"/><br> Email: <form:input path="email"/><br> Birth: <form:input path="birth"/><br> Salary: <form:input path="salary"/><br> <input type="submit" value="submit"> </form:form>
提交如图表单时,发现有错误将重定向到登陆页面
获得的错误信息将显示在页面上
也能够经过 path 属性指明要显示的表单项,从而使信息显示在相应的出错位置
改写 index.jsp 页面
<form:form action="emp" method="POST" modelAttribute="employee"> <form:errors path="lastName"/><br> LastName: <form:input path="lastName"/><br> <form:errors path="email"/><br> Email: <form:input path="email"/><br> <form:errors path="birth"/><br> Birth: <form:input path="birth"/><br><br> Salary: <form:input path="salary"/><br> <input type="submit" value="submit"> </form:form>
提交如图表单
提交结果为
须要注意的是,咱们要统一整个 IDE 的编码格式,例如统一设置为 UTF-8,否则在页面显示错误信息时会出现乱码现象。
虽然咱们已经实现了在页面上显示错误信息,但这些信息是框架根据规则自动生成的,缺少人性化和可读性。
咱们但愿的是能够显示本地化的错误信息,这就要用到 SpringMVC 为咱们提供的支持了。
经过国际化资源定制咱们的错误信息
每一个属性在数据绑定和数据发生错误时,都会生成一个对应的 FieldError 对象,当一个属性校验失败后,校验框架就会为该属性生成4个消息代码,这些代码以校验注解类名为前缀,结合类名、属性名及属性类型名产生多个对应的消息代码。
如在 Employee 类的 lastName 属性标注的 @NotEmpty 注解,当该注解值不知足 @NotEmpty 所定义的限制规则时,就会产生以下4个错误代码(@Email、@Past 相似)
NotEmpty.employee.lastName:根据类名、属性名产生的错误码
NotEmpty.lastName:根据属性名产生的错误码
NotEmpty.java.lang.String:根据类型产生的错误码
NotEmpty:根据验证注解名产生的错误码
当使用 SpringMVC 标签显示错误消息时,SpringMVC 会查看 WEB 上下文是否装配了对应的国际化消息,若是没有,则显示默认的错误消息,不然使用国际化消息。
具体作法
1. src 下新建国际化文件 i18n.properties
NotEmpty.employee.lastName=###### Email.employee.email=^^^^^^ Past.employee.birth=*******
2. 在spring 配置文件 springmvc.xml 中配置这个资源文件
<!-- 配置国际化资源文件 --> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="i18n"></property> </bean>
当咱们再次提交以下表单时
呈现的结果为
如此,页面的结果就按照咱们在资源文件中所配置的显示规则显示
值得注意的是,若是在数据类型转换或数据格式转换时发生错误,或者该有的参数不存在,或调用处理方法时发生错误,也都会在隐含模型中建立错误信息。
其错误代码前缀说明以下。
1. required:必要的参数,如 @RequestParam("param1")标注的一我的入参,可是请求参数不存在 param1 的参数
2. typeMismatch:在数据绑定时,发生数据类型不匹配的问题
3. methodInvocation:SpringMVC 在调用处理方法时发生了错误
以 typeMismatch 为例,在国际化文件中添加代码
typeMismatch.employee.birth=illegal date
提交以下表单
获得的结果页面为