簡化服務器驗證
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在調用處理方法時發生了錯誤