簡化服務器驗證
JSR-303 簡介
JSR-303 是 JavaEE 6 中的一項子規范,叫做 Bean Validation,官方參考實現是 Hibernate Validator。
此實現與 Hibernate ORM 沒有任何關系。JSR-303 用於對 Java Bean 中的字段的值進行驗證。 Spring MVC 3.x 之中也大力支持 JSR-303,可以在控制器中使用注解的方式對表單提交的數據方便地驗證。
Spring 4.0 開始支持 Bean Validation 功能。
JSR-303 基本的校驗規則
空檢查
@Null驗證對象是否為null@NotNull驗證對象是否不為null, 無法查檢長度為 0 的字符串@NotBlank檢查約束字符串是不是Null還有被Trim的長度是否大於 0,只對字符串,且會去掉前后空格@NotEmpty檢查約束元素是否為NULL或者是EMPTY
布爾檢查
@AssertTrue驗證Boolean對象是否為true@AssertFalse驗證Boolean對象是否為false
長度檢查
@Size(min=, max=)驗證對象(Array,Collection,Map,String)長度是否在給定的范圍之內@Length(min=, max=)驗證字符串長度介於min和max之間
日期檢查
@Past驗證Date和Calendar對象是否在當前時間之前,驗證成立的話被注釋的元素一定是一個過去的日期@Future驗證Date和Calendar對象是否在當前時間之后 ,驗證成立的話被注釋的元素一定是一個將來的日期
正則檢查
@Pattern驗證String對象是否符合正則表達式的規則,被注釋的元素符合制定的正則表達式regexp:正則表達式flags:指定Pattern.Flag的數組,表示正則表達式的相關選項
數值檢查
注意: 建議使用在 String ,Integer 類型,不建議使用在 int 類型上,因為表單值為 “” 時無法轉換為 int,但可以轉換為 String 為 “”,Integer 為 null
@Min驗證 Number 和 String 對象是否大等於指定的值@Max驗證 Number 和 String 對象是否小等於指定的值@DecimalMax被標注的值必須不大於約束中指定的最大值. 這個約束的參數是一個通過BigDecimal定義的最大值的字符串表示.小數存在精度@DecimalMin被標注的值必須不小於約束中指定的最小值. 這個約束的參數是一個通過BigDecimal定義的最小值的字符串表示.小數存在精度@Digits驗證 Number 和 String 的構成是否合法@Digits(integer=,fraction=)驗證字符串是否是符合指定格式的數字,integer指定整數精度,fraction指定小數精度@Range(min=, max=)被指定的元素必須在合適的范圍內@Range(min=10000,max=50000,message=”range.bean.wage”)@Valid遞歸的對關聯對象進行校驗, 如果關聯對象是個集合或者數組,那么對其中的元素進行遞歸校驗,如果是一個map,則對其中的值部分進行校驗.(是否進行遞歸驗證)@CreditCardNumber信用卡驗證@Email驗證是否是郵件地址,如果為null,不進行驗證,算通過驗證@ScriptAssert(lang= ,script=, alias=)@URL(protocol=,host=, port=,regexp=, flags=)
使用 Spring Validation 驗證
通過工具來進行驗證對象的合法性
POM
這里我們使用 Hibernate Validator 5.x 來實現 Spring Validation 接口,pom.xml 文件如下:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.3.4.Final</version>
</dependency>
定義驗證工具類
已為大家封裝好了工具類,享用即可。創建一個名為 BeanValidator 的工具類,代碼如下:
package com.funtl.my.shop.commons.validator;
import org.springframework.beans.factory.annotation.Autowired;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* JSR303 Validator(Hibernate Validator)工具類.
* <p>
* ConstraintViolation 中包含 propertyPath, message 和 invalidValue 等信息.
* 提供了各種 convert 方法,適合不同的 i18n 需求:
* 1. List<String>, String 內容為 message
* 2. List<String>, String 內容為 propertyPath + separator + message
* 3. Map<propertyPath, message>
* <p>
* 詳情見wiki: https://github.com/springside/springside4/wiki/HibernateValidator
*
* <p>Title: BeanValidator</p>
* <p>Description: </p>
*
* @author Lusifer
* @version 1.0.0
* @date 2018/6/26 17:21
*/
public class BeanValidator {
@Autowired //不能自動注入,靜態是屬性不能注入,這個注釋可以刪除,下面需要自己手動注入
private static Validator validator;
public static void setValidator(Validator validator) {
BeanValidator.validator = validator;
}
/**
* 調用 JSR303 的 validate 方法, 驗證失敗時拋出 ConstraintViolationException.
*/
private static void validateWithException(Validator validator, Object object, Class<?>... groups) throws ConstraintViolationException {
Set constraintViolations = validator.validate(object, groups);
if (!constraintViolations.isEmpty()) {
throw new ConstraintViolationException(constraintViolations);
}
}
/**
* 輔助方法, 轉換 ConstraintViolationException 中的 Set<ConstraintViolations> 中為 List<message>.
*/
private static List<String> extractMessage(ConstraintViolationException e) {
return extractMessage(e.getConstraintViolations());
}
/**
* 輔助方法, 轉換 Set<ConstraintViolation> 為 List<message>
*/
private static List<String> extractMessage(Set<? extends ConstraintViolation> constraintViolations) {
List<String> errorMessages = new ArrayList<>();
for (ConstraintViolation violation : constraintViolations) {
errorMessages.add(violation.getMessage());
}
return errorMessages;
}
/**
* 輔助方法, 轉換 ConstraintViolationException 中的 Set<ConstraintViolations> 為 Map<property, message>.
*/
private static Map<String, String> extractPropertyAndMessage(ConstraintViolationException e) {
return extractPropertyAndMessage(e.getConstraintViolations());
}
/**
* 輔助方法, 轉換 Set<ConstraintViolation> 為 Map<property, message>.
*/
private static Map<String, String> extractPropertyAndMessage(Set<? extends ConstraintViolation> constraintViolations) {
Map<String, String> errorMessages = new HashMap<>();
for (ConstraintViolation violation : constraintViolations) {
errorMessages.put(violation.getPropertyPath().toString(), violation.getMessage());
}
return errorMessages;
}
/**
* 輔助方法, 轉換 ConstraintViolationException 中的 Set<ConstraintViolations> 為 List<propertyPath message>.
*/
private static List<String> extractPropertyAndMessageAsList(ConstraintViolationException e) {
return extractPropertyAndMessageAsList(e.getConstraintViolations(), " ");
}
/**
* 輔助方法, 轉換 Set<ConstraintViolations> 為 List<propertyPath message>.
*/
private static List<String> extractPropertyAndMessageAsList(Set<? extends ConstraintViolation> constraintViolations) {
return extractPropertyAndMessageAsList(constraintViolations, " ");
}
/**
* 輔助方法, 轉換 ConstraintViolationException 中的 Set<ConstraintViolations> 為 List<propertyPath + separator + message>.
*/
private static List<String> extractPropertyAndMessageAsList(ConstraintViolationException e, String separator) {
return extractPropertyAndMessageAsList(e.getConstraintViolations(), separator);
}
/**
* 輔助方法, 轉換 Set<ConstraintViolation> 為 List<propertyPath + separator + message>.
*/
private static List<String> extractPropertyAndMessageAsList(Set<? extends ConstraintViolation> constraintViolations, String separator) {
List<String> errorMessages = new ArrayList<>();
for (ConstraintViolation violation : constraintViolations) {
errorMessages.add(violation.getPropertyPath() + separator + violation.getMessage());
}
return errorMessages;
}
/**
* 服務端參數有效性驗證
*
* @param object 驗證的實體對象
* @param groups 驗證組
* @return 驗證成功:返回 null;驗證失敗:返回錯誤信息
*/
public static String validator(Object object, Class<?>... groups) {
try {
validateWithException(validator, object, groups);
} catch (ConstraintViolationException ex) {
List<String> list = extractMessage(ex);
list.add(0, "數據驗證失敗:");
// 封裝錯誤消息為字符串
StringBuilder sb = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
String exMsg = list.get(i);
if (i != 0 ){
sb.append(String.format("%s. %s", i, exMsg)).append(list.size() > 1 ? "<br/>" : "");
} else {
sb.append(exMsg).append(list.size() > 1 ? "<br/>" : "");
}
}
return sb.toString();
}
return null;
}
}
修改實體類
修改實體類,增加驗證注解,以后我們只需要在實體類的屬性上使用 JSR-303 注解即可完成相關數據的驗證工作,關鍵代碼如下:
@Length(min = 6, max = 20, message = "用戶名長度必須介於 6 和 20 之間") private String username; @Length(min = 6, max = 20, message = "密碼長度必須介於 6 和 20 之間") private String password; @Pattern(regexp = RegexpUtils.PHONE, message = "手機號格式不正確") private String phone; @Pattern(regexp = RegexpUtils.EMAIL, message = "郵箱格式不正確") private String email;
注入工具類
修改 spring-context.xml 文件,注入 Validator 工具類,配置如下:
<!-- 配置 Bean Validator 定義 -->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean id="beanValidator" class="com.funtl.my.shop.commons.validator.BeanValidator">
<property name="validator" ref="validator" />
</bean>
效果演示
配置完成后,在瀏覽器端測試直接提交數據,效果如下:

使用Validator工具類進行驗證

補充
BindingResult可以使用Errors代替,這兩個類都位於org.springframework.validation包中。
使用注意:需校驗的Bean 對象和其綁定結果對象或錯誤對象時成對出現的,它們之間不允許聲明其他的入參。就是說 @Valid User user, BindingResult bindingResult,之間不能與其他參數。
通過 BindingResult 也可以獲取錯誤,但是需要添加@Valid注解,此時就不要之前的工具類
@RequestMapping(value = "/useradd",method = RequestMethod.POST)
public String addUser(@Valid User user, BindingResult bindingResult,Map<String,Object> map){
if (bindingResult.getErrorCount()>0){
for (FieldError error:bindingResult.getFieldErrors()){
if ("username".equals(error.getField())){
//String usernameMessage = error.getDefaultMessage();
//map.put("usernameMessage",usernameMessage);
}
}
//不需要重定向,還是放回之前的頁面
return "userForm";
}
return "login";
}
驗證處理
方式1
controller
@RequestMapping(value = "/useradd",method = RequestMethod.POST)
public String addUser(@Valid User user, BindingResult bindingResult,Map<String,Object> map){
if (bindingResult.getErrorCount()>0){
for (FieldError error:bindingResult.getFieldErrors()){
if ("username".equals(error.getField())){
String usernameMessage = error.getDefaultMessage();
map.put("usernameMessage",usernameMessage); //給請求域中添加錯誤消息
}
}
//不需要重定向,還是放回之前的頁面
return "userForm";
}
return "login";
}
html,獲取錯誤消息
<c:if test="${usernameMessage!=null}">${usernameMessage}</c:if>
方式2
@RequestMapping(value = "/useradd",method = RequestMethod.POST)
public String addUser(@Valid User user, BindingResult bindingResult,Map<String,Object> map){
if (bindingResult.getErrorCount()>0){
//不需要重定向,還是放回之前的頁面
return "userForm";
}
return "login";
}
html,使用<form:errors>來獲取錯誤
<form:errors path="username"></form:errors>//username 就是我們驗證的字段(添加注解的字段),獲取某一個錯誤 <form:errors path="*"></form:errors>//獲取所有的錯誤
提示消息國際化(驗證處理方式2使用)
原理
每個屬性在數據綁定和數據校驗發生錯誤時,都會生成一個對應的FieldError對象。
當一個屬性校驗失敗后,校驗框架會為該屬性生成4個消息代碼,這些代碼以校驗注解類名為前綴,結合modleAttribute、屬性名及屬性類型名生成多個對應的消息代碼:例如User 類中的 password 屬性注解了一個@Pattern注解,當該屬性值不滿足@Pattern所定義的規則時,就會產生以下4個錯誤代碼:
Pattern.user.password
Pattern.password
Pattern.java.lang.String
Pattern
當使用Spring MVC標簽(<form:errors path="username"></form:errors>)顯示錯誤消息時,Spring MVC會查看WEB 上下文是否裝配了對應的國際化消息,如果沒有,則顯示默認的錯誤消息,否則使用國際化消息。
修改實體類
@Length(min = 6, max = 20) //不需要我們添加message private String username;
spring-MVC.xml
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename" value="info"></property>
</bean>
info_zh_CN.properties
Length.user.username=\u4e0d\u80fd\u4e3a\u7a7a\u54e6
key的格式:注解.類.字段
補充
若數據類型轉換或數據格式轉換時發生錯誤,或該有的參數不存在,或調用處理方法時發生錯誤,都會在隱含模型中創建錯誤消息。其錯誤代碼前綴(都是小寫)說明如下:
-required:必要的參數不存在。如@RequiredParam("param11)標注了一個入參,但是該參數不存在
-typeMismatch:在數據綁定時,發生數據類型不匹配的問題
-methodInvocation:Spring MVC在調用處理方法時發生了錯誤
