1. 程式人生 > >Spring Hibernate Validation

Spring Hibernate Validation

資料校驗是 Web 應用為了安全必須處理的步驟,Spring MVC 提供了兩種方法來對使用者的輸入資料進行校驗,一種是 Spring 自帶的 Validation 校驗框架,另一種是利用 JRS-303 驗證框架進行驗證。

在實際開發中我們不是使用 Spring 自帶的框架,而是使用 JRS 相關驗證框架(Hibernate validator)完成開發。 Hibernate-validator 是根據 Bean Validation 的參考實現,遵循 Bean Validation 2.0 規範,基於 JSR 380 實現。

1 Bean Validation website

Bean Validation 中內建的 constraint 如下:

註解 作用
@Valid 被註釋的元素是一個物件,需要檢查此物件的所有欄位值
@Null 被註釋的元素必須為 null
@NotNull 被註釋的任何元素必須不為 null
@AssertTrue 被註釋的元素必須為 true
@AssertFalse 被註釋的元素必須為 false
@Min(value) 被註釋的元素必須是一個數字,其值必須大於等於指定的最小值
@Max(value) 被註釋的元素必須是一個數字,其值必須小於等於指定的最大值
@DecimalMin(value) 被註釋的元素必須是一個數字,其值必須大於等於指定的最小值
@DecimalMax(value) 被註釋的元素必須是一個數字,其值必須小於等於指定的最大值
@Negative 被註釋的元素必須是一個負數
@NegativeOrZero 被註釋的元素必須是負數或 0
@Positive 被註釋的元素必須是一個正數
@PositiveOrZero 被註釋的元素必須是一個正數或 0
@Size(max, min) 被註釋的元素的大小必須在指定的範圍內
@Digits (integer, fraction) 被註釋的元素必須是一個數字,其值必須在可接受的範圍內
@Past 被註釋的元素必須是一個過去的日期
@PastOrPresent 被註釋的元素必須是一個過去或當前的日期
@Future 被註釋的元素必須是一個將來的日期
@FutureOrPresent 被註釋的元素必須是一個將來或當前的日期
@Pattern(value) 被註釋的元素必須符合指定的正則表示式
@NotEmpty 集合物件的元素不為0,即集合不為空,也可以用於字串不為 null
@NotBlank 只能用於字串不為null,並且字串trim()以後length要大於0
@Email 被註釋的元素必須是一個有效的郵箱地址

2 Hibernate Validator

Hibernate Validator 完全遵循了 Bean Validation 的規範,並在其基礎上有附加的擴充套件。

註解 作用
@CreditCardNumber(ignoreNonDigitCharacters=) 被註釋的字串必須通過 Luhn 校驗演算法,銀行卡,信用卡等號碼一般都用 Luhn 計算合法性
@Currency(value=) 被註釋的 javax.money.MonetaryAmount 貨幣元素是否合規
@DurationMax(days=, hours=, minutes=, seconds=, millis=, nanos=, inclusive=) 被註釋的元素不能大於指定日期
@DurationMin(days=, hours=, minutes=, seconds=, millis=, nanos=, inclusive=) 被註釋的元素不能低於指定日期
@EAN 被註釋的元素是否是一個有效的 EAN 條形碼
@Length(min=, max=) 被註釋的字串的大小必須在指定的範圍內
@LuhnCheck(startIndex= , endIndex=, checkDigitIndex=, ignoreNonDigitCharacters=) Luhn 演算法校驗字串中指定的部分
@Mod10Check(multiplier=, weight=, startIndex=, endIndex=, checkDigitIndex=, ignoreNonDigitCharacters=) Mod10 演算法校驗
@Mod11Check(threshold=, startIndex=, endIndex=, checkDigitIndex=, ignoreNonDigitCharacters=, treatCheck10As=, treatCheck11As=) Mod11 演算法校驗
@Range(min=, max=) 被註釋的元素必須在合適的範圍內
@SafeHtml(whitelistType= , additionalTags=, additionalTagsWithAttributes=, baseURI=) classpath中要有jsoup包
@ScriptAssert(lang=, script=, alias=, reportOn=) 檢查指令碼是否可執行
@URL(protocol=, host=, port=, regexp=, flags=) 被註釋的字串必須是一個有效的url

3 Test

3.1 Manev 導包

使用 manev 匯入相關包。 這裡只展示 spring-mvc 和 hibernate-validator 包。

<dependencies>
    <!-- spring-mvc-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.0.0.RELEASE</version>
    </dependency>
    <!-- hibernate-validator -->
    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>6.0.2.Final</version>
    </dependency>
</dependencies>

3.2 國際化配置

Hibernate Validator 預設國際化檔案是 ValidationMessages.properties,存放的位置在 org.hibernate.validator,我們可以在專案根目錄增加此預設檔案自動替換達 Hibernate 的資原始檔達到國際化的目的。多數情況專案是外掛開發,各大業務線是自定義國際化檔案的。這裡我們新增一個檔案 ValidationMessages_zh_CN.properties。

username.NotEmpty = 使用者名稱不能為空
password.NotEmpty = 密碼不能為空
password.Size = 密碼長度應在{min}-{max}個字元

3.3 Xml 配置

3.3.1 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="yjcocoa"
         version="3.1">

    <!-- 配置spring mvc 前端核心控制器-->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- 系統預設頁面-->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

</web-app>

3.3.2 springmvc-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<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"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="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.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- 配置檢視直譯器ViewResolver -->
    <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <!-- 配置掃描器 -->
    <context:component-scan base-package="com.springmvc"/>
    <!--配置註解驅動  -->
    <mvc:annotation-driven/>

    <!--配置靜態資源的訪問對映,此配置中的檔案,將不被前端控制器攔截 -->
    <mvc:resources location="/js/" mapping="/js/**"/>

    <!-- 配置校驗器 -->
    <!--國際化校驗資源-->
    <bean id="hibernateValidationMessages" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="defaultEncoding" value="UTF-8"/>
        <property name="basenames">
            <list>
                <!--多模組自定義校驗文件-->
                <value>ValidationMessages</value>
            </list>
        </property>
    </bean>
    <!--屬性校驗-->
    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
        <!-- 校驗器,使用hibernate校驗器 -->
        <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
        <property name="validationMessageSource" ref="hibernateValidationMessages"/>
    </bean>
    <!--方法校驗-->
    <bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
        <property name="validator" ref="validator"/>
    </bean>

</beans>

除了對例項類的屬性校驗,我們還可以通過 MethodValidationPostProcess 對方法引數進行校驗。這樣我們就不需要分組校驗,只需要自定義方法即可達到分組校驗的效果,進一步精簡了程式碼。Spring 銜接 Hibernate Validator 只需配置一個 validator Bean 元素即可,並制定 bean 名稱 validator,spring mvc 會預設載入 validator 校驗器。國際化主要通過 ResourceBundleMessageSource 載入資原始檔並注入 LocalValidatorFactoryBean 中。

3.4 JSP 頁面

頁面主要是index.jsp,其中 success.jsp就不再展示。

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8" %>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>登入</title>
</head>
<body>
<form action="${pageContext.request.contextPath }/validator/login"
      method="post">
    使用者名稱:<input type="text" name="username" value=${user.username}> <span>${usernameValid}</span><br/>
    密&nbsp;&nbsp;&nbsp;碼:<input type="text" name="password" value=${user.password}><span>${passwordValid}</span><br/>
    <input type="submit" value="登入"/>
</form>
</body>
</html>

頁面使用 from 提交。並使用 ${} 獲取資料。

3.5 Java 類

3.5.1 PO類

package com.springmvc.po;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;

public class User {

    @NotEmpty(message = "{username.NotEmpty}")
    private String username;

    @NotEmpty(message = "{password.NotEmpty}")
    @Size(min = 6, max = 9, message = "{password.Size}")
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}

這裡可直接看到註解校驗的好處,開發簡單,可維護性很高。國際化資原始檔的寫法使用了 {key} 的方式。

3.5.2 控制器

package com.springmvc.controller;

import com.springmvc.po.User;
import com.springmvc.service.ValidatorService;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Valid;

/**
 * ValidatorController.java
 * <p>
 * Created by 陽君 on 2017/10/16.
 * Copyright © 2017年 springmvc. All rights reserved.
 */
@Controller
@RequestMapping("/validator")
public class ValidatorController {

    @Autowired
    private ValidatorService validatorService;

    @RequestMapping("")
    public String index() {
        return "validator/index";
    }

    @RequestMapping("/login")
    public String login(Model model, @Valid User user, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            for (ObjectError objectError : bindingResult.getAllErrors()) {
                FieldError fieldError = (FieldError) objectError;
                model.addAttribute(fieldError.getField() + "Valid", objectError.getDefaultMessage());
            }
            return "validator/index";
        }
        model.addAttribute("msg", user.toString());
        return "validator/success";
    }

    @RequestMapping("/login2")
    public String login2(Model model, User user) {
        try {
            model.addAttribute("msg", this.validatorService.checkUser(user.getUsername(), user.getPassword()));
        } catch (ConstraintViolationException e) {
            for (ConstraintViolation constraintViolation : e.getConstraintViolations()) {
                String key = ((PathImpl) constraintViolation.getPropertyPath()).getLeafNode().getName() + "Valid";
                model.addAttribute(key, constraintViolation.getMessage());
            }
            return "validator/index";
        }
        return "validator/success";
    }

}

這裡使用 fieldError.getField() + "Valid" 組成統一格式的校驗反饋。login 實現了例項化的屬性校驗,login2 通過 validatorService 完成了方法的校驗。

3.5.3 業務層

package com.springmvc.service;

import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import javax.validation.ConstraintViolationException;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Size;

/**
 * ValidatorService.java
 * <p>
 * Created by 陽君 on 2017/10/16.
 * Copyright © 2017年 springmvc. All rights reserved.
 */
@Validated
@Service
public class ValidatorService {

    public String checkUser(@NotEmpty(message = "{username.NotEmpty}")
                                    String username,
                            @NotEmpty(message = "{password.NotEmpty}")
                            @Size(min = 6, max = 9, message = "{password.Size}")
                                    String password) throws ConstraintViolationException {
        return "User [username=" + username + ", password=" + password + "]";
    }

}

業務層的 checkUser 方法實現了方法校驗,當校驗不通過時會報 ConstraintViolationException 錯誤。

3.5 效果圖

執行專案,點登入,即可看見如下效果。

Appendix

Sample Code

Java

Revision History

時間 描述
2017-10-17 博文完成