Spring MVC----Validation(數據校驗)


簡化服務器驗證

 

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=) 驗證對象(ArrayCollection , MapString)長度是否在給定的范圍之內
  • @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在調用處理方法時發生了錯誤

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM