必定要統一各個 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
提交以下表單
獲得的結果頁面爲