9.1 數據校驗
應用通過輸入頁面收集的數據是非常複雜的,不僅會包含正常用戶的誤輸入,還可能包含惡意用戶的惡意輸入。異常的輸入,輕則會導致系統非正常中斷,重則會導致系統崩潰。應用程序必須能正常處理表現層接收的各種數據,通常的做法是遇到異常輸入時應用程序直接返回,提示用戶必須重新輸入,也就是將哪些異常輸入過濾掉。這種對異常輸入的過濾,就是輸入校驗,也稱爲“數據校驗”。一個健壯的應用系統必須將這些非法輸入阻止在應用外,防止這些非法輸入進入系統,這樣才能保證系統不受影響。
輸入校驗分爲客戶端校驗和服務器端校驗,客戶端校驗主要是過濾非正常用戶的誤操作,通常通過 JavaScript 代碼完成;服務器端校驗是整個應用阻止非法數據的最後防線,主要通過在應用中編程實現。
客戶端校驗的主要作用是防止用戶的誤輸入,這僅能對輸入進行初步過濾;對於惡意用戶的惡意行爲,客戶端校驗無能爲力。因此,客戶端校驗絕不可替代服務器校驗。當然客戶端校驗也絕不可少,因爲 Web 應用的大部分用戶是正常用戶,他們的輸入可能包含了大量的誤輸入,客戶端校驗能把這些誤輸入阻止在客戶端,從而降低了服務器的負載。
Spring MVC 提供了強大的數據校驗功能,其中有兩種方法可以驗證輸入:一種是利用 Spring 自帶的 Validation 校驗框架;另一種是利用 JSR 303 (Java 驗證規範)實現校驗功能。
9.1.1 Spring 的 Validation 校驗框架
Spring 擁有自己獨立的數據校驗框架。Spring 在進行數據綁定時,可同時調用校驗框架來完成數據校驗工作。
Spring 的校驗框架在 org.springframework.validation 包中,其中重要的接口和類如下:
- Validation。最重要的接口。
- Errors。Spring 用來存放錯誤信息的接口。
- ValidationUtils。Spring 提供的一個關於校驗的工具類。
- LocalValidationFactoryBean。位於 org.springframework.validation.beanvalidation 包中,該類既實現了 Spring 的 Validation 接口,也實現了 JSR 303 的 Validation 接口。只要在 Spring 容器定義一個 LocalValidationFactorybean,即可將其注入到需要數據校驗的 Bean 中。定義一個 LocalValidationFactoryBean 的 Bean 非常簡單:
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<mvc:annotation-driven/>
會默認裝配好一個 LocalValidationFactoryBean,所以在實際開發中不需要手動配置 LocalValidationFactoryBean。需要注意的是:Spring 本身沒有提供 JSR 303 的實現,如果要使用 JSR 303 完成驗證,則必須將 JSR 303 的實現(注入 Hibernate Validator)jar 文件加入到應用程序的類路徑下,這樣 Spring 會自動加載並裝配好 JSR 303 的實現。
示例:Spring 的 Validation 校驗
- 新建 loginForm.jsp,頁面使用
<form:errors>
標籤顯示屬性的錯誤信息。
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>測試 Validation 接口驗證</title>
</head>
<body>
<jsp:useBean id="user" class="domain.User" scope="request"/>
<form:form modelAttribute="user" method="post" action="login">
<table>
<tr>
<td><label>登錄名:</label></td>
<td><form:input path="loginname"/></td>
<!-- 顯示 loginname 屬性的錯誤信息 -->
<td><form:errors path="loginname" cssStyle="color:red"/></td>
</tr>
<tr>
<td><label>密碼:</label></td>
<td><form:input path="password"/></td>
<!-- 顯示 password 屬性的錯誤信息 -->
<td><form:errors path="password" cssStyle="color:red"/> </td>
</tr>
<tr>
<td><input type="submit" value="提交"></td>
</tr>
</table>
</form:form>
</body>
</html>
- 新建 User 類負責接收頁面輸入信息。
package domain;
import java.io.Serializable;
public class User implements Serializable {
private String loginname;
private String password;
public String getLoginname() {
return loginname;
}
public void setLoginname(String loginname) {
this.loginname = loginname;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"loginname='" + loginname + '\'' +
", password='" + password + '\'' +
'}';
}
}
- 新建 UserValidator 類。
package validator;
import domain.User;
import org.springframework.stereotype.Repository;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
// 實現 Spring 的 Validator 接口
@Repository("userValidator")
public class UserValidator implements Validator {
// 從該校驗器能夠對 clazz 類型的對象進行校驗。
@Override
public boolean supports(Class<?> aClass) {
// User 指定的 Class 參數所表示的類或接口是否相同,或是否是其超類或超接口。
return User.class.isAssignableFrom(aClass);
}
// 對目標類 target 進行校驗,並將校驗錯誤記錄在 errors 當中。
@Override
public void validate(Object target, Errors errors) {
/*
* 使用 ValidationUtils 中的一個靜態方法rejectIfEmpty() 來對 loginname 屬性進行數據校驗,
* 假若 'loginname' 屬性是 null 或者空字符串的話,就拒絕驗證通過。
*/
ValidationUtils.rejectIfEmpty(errors,"loginname",null,"登錄名不能爲空!");
ValidationUtils.rejectIfEmpty(errors,"password",null,"密碼不能爲空!");
User user = (User)target;
if (user.getLoginname().length() > 10){
// 使用 Errors 的 rejectValue 方法驗證
errors.rejectValue("loginname",null,"用戶名不能超過十個字符!");
}
if (user.getPassword() != null
&& !user.getPassword().equals("")
&& user.getPassword().length() < 6){
errors.rejectValue("password",null,"密碼不能小於6位!");
}
}
}
UserValidator 實現了 Spring 的 Validator 接口,其可以對 User 對象進行數據校驗,並分別使用 ValidationUtils 的 rejectIfEmpty 方法和 Errors 的 rejectValue 方法對 User 進行了數據校驗。@Repository(“userValidator”)註解將該對象註釋爲 Spring 容器中的一個 Bean,名字爲 “userValidator”。
- 新建 UserController 類控制頁面跳轉。
package controller;
import domain.User;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import validator.UserValidator;
@Controller
public class UserController {
private final static Log logger = LogFactory.getLog(UserController.class);
@RequestMapping(value = "/{formName}")
public String loginForm(@PathVariable String formName){
// 動態跳轉頁面
return formName;
}
// 注入 UserValidator 對象
@Autowired
@Qualifier(value = "userValidator")
private UserValidator userValidator;
@RequestMapping(value = "/login",method = RequestMethod.POST)
public String login(
@ModelAttribute User user,
Model model,
Errors errors
){
logger.info(user);
model.addAttribute("user",user);
// 調用 userValidator 的驗證方法
userValidator.validate(user,errors);
// 如果驗證不通過跳轉到 loginForm 視圖
if (errors.hasErrors()){
return "loginForm";
}
return "success";
}
}
login 方法將對傳進來的參數進行校驗,注意方法的最後一個參數 errors,該參數是一個 Spring 校驗框架的 Errors 對象。在該方法中調用了之前寫的 userValidator 類進行數據校驗,如果校驗失敗,則跳轉到“loginForm”視圖。
- 新建 success.jsp 頁面。
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>成功</title>
</head>
<body>
<form:form modelAttribute="user" method="post" action="">
<table>
<tr>
<td><label>用戶名:</label></td>
<td><form:input path="loginname"/></td>
</tr>
<tr>
<td><label>密碼:</label></td>
<td><form:input path="password"/></td>
</tr>
</table>
</form:form>
</body>
</html>
- 修改 Spring MVC 控制文件。
<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="controller"/>
<!-- 裝配自定義的類型轉換器 -->
<mvc:annotation-driven/>
<!-- 使用默認的 Servlet 來響應靜態文件 -->
<mvc:default-servlet-handler/>
<!-- 視圖解析器 -->
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前綴 -->
<property name="prefix">
<value>/WEB-INF/content/</value>
</property>
<!-- 後綴 -->
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
- 修改 Spring 配置文件
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="validator"/>
</beans>
- 修改 web.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
- 部署 ValidatorTest 應用,在瀏覽器中輸入如下 URL 測試應用:
http://localhost:8080/ValidatorTest/loginForm
不輸入登錄名和密碼直接提交,結果如下所示:
輸入不合法的登錄名和密碼,提交後結果如下所示:
可以看到,校驗框架校驗後返回了錯誤信息並將其顯示在頁面上。
由於早期 Spring 就被設計了 Validation 框架,所以之前的很多應用都使用 Validation 框架進行校驗。由於 Validation 框架通過硬編碼完成數據校驗,在實際開發中會顯得比較麻煩,因此現代開發更加推薦使用 JSR 303 完成數據校驗。
注: 起初運行程序時,程序報錯爲:
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'user' available as request attribute
翻譯爲:
java.lang.IllegalStateException:BindingResult和bean名“user”的純目標對象都不可用作請求屬性
分析後發現,報錯位置應在<form:form modelAttribute="user" method="post" action="login">
,在 loginForm.jsp 頁面調用 user 時 user 並未被實例化,因此,頁面無法調用。
解決辦法:
在 loginForm.jsp 頁面中加入如下代碼:
<jsp:useBean id="user" class="domain.User" scope="request"/>
9.1.2 JSR 303 校驗
JSR 303 是 Java 爲 Bean 數據合法性校驗所提供的一個標準規範,叫做 Bean Validation。官方參考實現是 Hibernate Validator。
Bean Validation 爲 JavaBean 驗證定義了相應的元數據類型和 API。在應用程序中,通過在 Bean 屬性上標註類似於 @NotNull、@Max 等標準的註解指定校驗規則,並通過標註的驗證接口對 Bean 進行驗證。Bean Validation 是一個運行時的數據驗證框架,在驗證之後驗證的錯誤信息會被馬上返回。
JSR 303 是一個規範,它的核心接口是 javax.validation.Validator,該接口根據目標對象中所標註的校驗註解進行數據校驗,並得到校驗結果。JSR 303 目前有兩個實現,一個是 Hibernate Validator,另一個是 Apache bval。
JSR 303 中定義了一套可標註在成員變量、屬性方法上的校驗註解。
註解 | 功能 | 範例 |
---|---|---|
@Null | 驗證對象是否爲 null。 | @Null String desc; |
@NotNull | 驗證對象是否不爲 null,無法檢查長度爲 0 的字符串,用於驗證基本數據類型。 | @NotNull String name; |
@AssertTrue | 驗證 Boolean 對象是否爲 true。 | @AssertTrue boolean isEmpty; |
@Max(value) | 驗證 Number 和 String 對象是否小於等於指定的值。 | @Max(18) int age; |
@Min(value) | 驗證 Number 和 String 對象是否大於等於指定的值。 | @Min(10) int age; |
@DecimalMax(value) | 被標註的值必須不大於約束中指定的最大值。這個約束的參數是一個通過 BigDecimal 定義的最大值的字符串表示,小數存在精度。 | @DecimalMax(1.1) BigDecimal price; |
@DecimalMin(value) | 被標註的值必須不小於約束中指定的最小值。這個約束的參數是一個通過 BigDecimal 定義的最大值的字符串表示,小數存在精度。 | @DecimalMin(0.5) BigDecimal Price; |
@Digits(integer,fraction) | 驗證字符串是否是符合指定格式的數字,integer 指定整數精度,fraction 指定小數精度。 | @Digits(integer=5,fraction=2) BigDecimal price; |
@Size(min,max) | 驗證對象(Array、Collection、Map、String)長度是否在給定的範圍之內。 | @Size(min=15,max=60) int age; |
@Past | 驗證 Date 和 Callendar 對象是否在當前時間之前。 | @Past Date birthDate; |
@Future | 驗證 Date 和 Callendar 對象是否在當前時間之後。 | @Future Date ShippingDate; |
@Pattern | 驗證 String 對象是否符合正則表達式的規則。 | @Pattern(regexp="[1][3,8][3,6,9][0-9]{8}") String phone; |
Hibernate Validator 是 JSR 303 的一個參考實現,除了支持所有標準的校驗註解之外,它還擴展瞭如下註解:
註解 | 功能 | 範例 |
---|---|---|
@NotBlank | 檢查約束字符串是不是 Null,被 Trim 的長度是否大於 0。只對字符串,且會去掉前後空格。 | @NotBlank String name; |
@URL | 驗證是否是合法的 url。 | @URL String url; |
驗證是否是合法的郵件地址。 | @Email String email; |
|
@CreditCardNumber | 驗證是否是合法的信用卡號碼。 | @CreditCardNumber String creditCard; |
@Length(min,max) | 驗證字符串的長度必須在指定的範圍內。 | @Length(min=6,max=8) String password; |
@NotEmpty | 檢查元素是否爲 NULL 或者 EMPTY。用於 Array、Collection、Map、String。 | @NotEmpty String name; |
@Range(min,max,message) | 驗證屬性值必須在合適的範圍內。 | @Range(min=18,max=60,message=“學生的年齡必須在 18 歲到 60 歲之間”) int age; |
示例:測試 JSR 303 校驗
本例使用的是 Hibernate Validator 的實現,創建 JSR303Test 工程後,將 hibernate-validator-5.2.4.Final.jar、hibernate-validator-annotation-processor-5.2.4.Final.jar、hibernate-validator-cdi-5.2.4.Final.jar、validation-api-1.1.0.Final.jar、classmate-1.1.0.jar 和 jboss-logging-3.2.1.Final.jar 加入項目當中。
- 新建 registerForm.jsp 註冊頁面,用於提交用戶註冊信息,註冊信息包括用戶名、密碼、郵箱、電話等。之後將在後臺使用 JSR 303 進行驗證。
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>測試 JSR 303</title>
</head>
<body>
<h3>註冊頁面</h3>
<form:form modelAttribute="user" method="post" action="login">
<table>
<tr>
<td>登錄名:</td>
<td><form:input path="loginname"/></td>
<td><form:errors path="loginname" cssStyle="color:red"/></td>
</tr>
<tr>
<td>密碼:</td>
<td><form:input path="password"/></td>
<td><form:errors path="password" cssStyle="color:red"/></td>
</tr>
<tr>
<td>用戶名:</td>
<td><form:input path="username"/></td>
<td><form:errors path="username" cssStyle="color:red"/></td>
</tr>
<tr>
<td>年齡:</td>
<td><form:input path="age"/></td>
<td><form:errors path="age" cssStyle="color:red"/></td>
</tr>
<tr>
<td>郵箱:</td>
<td><form:input path="email"/></td>
<td><form:errors path="email" cssStyle="color:red"/></td>
</tr>
<tr>
<td>生日:</td>
<td><form:input path="birthday"/></td>
<td><form:errors path="birthday" cssStyle="color:red"/></td>
</tr>
<tr>
<td>電話:</td>
<td><form:input path="phone"/></td>
<td><form:errors path="phone" cssStyle="color:red"/></td>
</tr>
<tr>
<td><input type="submit" value="提交"/></td>
</tr>
</table>
</form:form>
</body>
</html>
- 新建 User 類用於接收用戶輸入信息,User 類使用了 Hibernate Validator 的註解對前臺提交的數據進行驗證。
package domain;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotBlank;
import org.hibernate.validator.constraints.Range;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.Past;
import javax.validation.constraints.Pattern;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable {
@NotBlank(message="登錄名不能爲空")
private String loginname;
@NotBlank(message = "密碼不能爲空")
@Length(min = 6,max = 8,message = "密碼長度必須在 6 位到 8 位之間")
private String password;
@NotBlank(message = "用戶名不能爲空")
private String username;
@NotBlank(message = "年齡不能爲空")
@Range(min = 15,max = 60,message = "年齡必須在15歲到60歲之間")
private Integer age;
@Email(message = "必須是合法的郵箱地址")
private String email;
@DateTimeFormat(pattern = "yyyy-MM-dd")
@Past(message = "生日必須是一個過去的日期")
private Date birthday;
@Pattern(regexp = "[1][3,8][3,6,9][0-9]{8}",message = "無效的電話號碼")
private String phone;
public String getLoginname() {
return loginname;
}
public void setLoginname(String loginname) {
this.loginname = loginname;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
- 新建 UserController 類控制頁面跳轉。UserController中使用 @Valid 註解對提交的數據進行校驗,後面跟着 Errors 對象保存校驗信息。如果 errors 中有錯誤信息,則返回 registerForm.jsp 頁面,驗證通過則跳轉到 success.jsp 頁面。
package controller;
import domain.User;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.validation.Valid;
@Controller
public class UserController {
private final static Log logger = LogFactory.getLog(UserController.class);
@RequestMapping(value = "/{formName}")
public String loginForm(@PathVariable String formName, Model model){
User user = new User();
model.addAttribute("user",user);
// 動態跳轉頁面。
return formName;
}
// 數據校驗使用 @Valid,後面跟着 Errors 對象保存校驗信息。
@RequestMapping(value = "/login",method = RequestMethod.POST)
public String login(
@Valid @ModelAttribute User user,
Errors errors,
Model model){
logger.info(user);
if (errors.hasErrors()){
return "registerForm";
}
model.addAttribute("user",user);
return "success";
}
}
- 新建 success.jsp 頁面,用於顯示數據校驗通過的結果。
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>測試 JSR 303</title>
</head>
<body>
<h3>測試 JSR 303</h3>
登錄名:${requestScope.user.loginname}<br>
密碼:${requestScope.user.password}<br>
用戶名:${requestScope.user.username}<br>
年齡:${requestScope.user.age}<br>
郵箱:${requestScope.user.email}<br>
生日:<fmt:formatDate value="${requestScope.user.birthday}" pattern="yyyy-MM-dd"/><br>
電話:${requestScope.user.phone}<br>
</body>
</html>
- 修改 Spring MVC 配置文件。由於
<mvc:annotation-driven/>
會默認裝配好一個 LocalValidatorFactoryBean,因此 Spring MVC 配置文件不需增加額外的配置。
<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="controller"/>
<!-- 裝配自定義的類型轉換器 -->
<mvc:annotation-driven/>
<!-- 使用默認的 Servlet 來響應靜態文件 -->
<mvc:default-servlet-handler/>
<!-- 視圖解析器 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 前綴 -->
<property name="prefix">
<value>/WEB-INF/content/</value>
</property>
<!-- 後綴 -->
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
- 修改 web.xml 文件。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
- 部署 JSR303Test 應用,在瀏覽器中輸入如下 URL:
http://localhost:8080/JSR303Test/registerForm
結果如下圖所示:
輸入錯誤信息,直接單擊提交,後臺驗證不通過,如下圖所示:
輸入符合校驗規則的註冊信息,通過驗證後會跳轉到 success 頁面。如下圖所示:
以上使用註解的 message 屬性輸出錯誤信息,而在實際項目中,我們希望錯誤信息更加人性化、更具可讀性,同時還希望顯示國際化的錯誤信息。
Spring MVC 支持國際化顯示數據校驗的錯誤信息。每個屬性在數據綁定和數據校驗發生錯誤時,都會生成一個對應的 FieldError 對象,FieldError 對象實現了 org.springframework.context.MessageSourceResolvable 接口,顧名思義,MessageSourceResolvable 是可用國際化資源進行解析的對象。MessageSourceResolvable 接口有如下3個方法:
- Object[] getArguments()。返回一組參數對象。
- String[] getCodes()。返回一組消息代碼,每一個代碼對應一個屬性資源,可以使用 getArguments() 返回的參數對資源屬性進行參數替換。
- String getDefaultMessage()。默認的消息,如果沒有裝配相應的國際化資源,那麼顯示的所有錯誤信息都是默認的。
當一個屬性校驗失敗後,校驗框架會爲該屬性生成4個消息代碼,這些代碼以校驗註解類名爲前綴,結合類名、屬性名以及屬性類型名生成多個對應的消息代碼。
當使用 Spring MVC 標籤顯示錯誤信息時,Spring MVC 會查看 Web 上下文是否裝配了對應的國際化消息,如果沒有,則顯示默認的錯誤信息,否則使用國際化消息對錯誤代碼進行顯示。
知道錯誤對象的錯誤碼是對應國際化消息的鍵名稱後,定義兩個國際化資源文件,在國際化資源文件中爲錯誤代碼定義相應的本地化消息內容。
文件名爲:message_en_US.properties
NotBlank.user.loginname= 登錄名不能爲空
NotBlank.user.password= 密碼不能爲空
Length.user.password= 密碼長度必須在6位到8位之間
NotBlank.user.username= 用戶名不能爲空
Range.user.age= 年齡必須在15歲到60歲之間
Email.user.email= 必須是合法的郵箱地址
Past.user.birthday= 生日必須是一個過去的日期
Pattern.user.phone= 無效的電話號碼
文件名爲:message_zh_CN.properties
NotBlank.user.loginname = Loginname is not null
NotBlank.user.password = Password is not null
Length.user.password = Password length must be between 6 and 8
NotBlank.user.username = Username is not null
Range.user.age = Age must be between the ages of 15 to 60
Email.user.email = Must be a legitimate email address
Past.user.birthday = Birthday must be a date in the past
Pattern.user.phone = Invalid phone number
接下來在 Spring MVC 配置文件中增加國際化配置。
通過該選項創建屬性文件。然後在 Spring MVC 配置文件中增加國際化配置。
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<!-- 國際化資源文件 -->
<property name="basename" value="message"/>
</bean>