Spring Security技術棧學習筆記(三)表單校驗以及自定義校驗註解開發

Hibernate不僅僅爲操作數據庫提供瞭解決方案,還爲數據校驗提供瞭解決方案——Hibernate Validator。本篇博客將介紹常用的Validator註解的使用以及在Validator不滿足實際需求的情況下如何使用自定義Validator來實現數據校驗。

一、常見的數據校驗註解

首先我們需要在項目的POM文件中添加Hibernate Validator的依賴纔可以使用它的數據校驗器進行數據校驗。由於Spring Boot已經將Hibernate Validator集成到了spring-boot-starter-web包裏,所以這裏不需要額外引用Hibernate Validator依賴。常用的校驗註解下表所示:

註解 說明
@NotNull 值不能爲空
@Null 值必須爲空
@Pattern(regex=) 字符串必須匹配正則表達式
@Size(min=, max=) 集合元素數量必須在minmax之間
@CreditCardNumber(ignoreNonDigitCharaters=) 字符串必須是信用卡號(美國標準信用卡)
@Email 字符串必須是Email地址
@Length(min=, max=) 字符串長度必須在minmax之間
@NotBlank 字符串必須有字符
@NotEmpty 字符串不爲null,集合必須有元素
@Range(min=, max=) 數字必須在minmax之間
@SafeHtml 字符串是安全的HTML
@URL 字符串是合法的URL
@AssertFalse 值必須是false
@AssertTrue 值必須是true
@DecimalMax(value=, inclusive=) 如果inclusive=true,那麼值必須大於等於value,如果inclusive=false,那麼值必須大於value
@DecimalMin(value=, inclusive=) 如果inclusive=true,那麼值必須小於等於value,如果inclusive=false,那麼值必須小於value
@Digits(integer=, fraction=) 數字格式檢查,integer是指整數部分最大長度,fraction是指小數部分最大長度
@Future 值必須是未來的日期
@Past 值必須是過去的日期
@Max(value=) 值必須小於等於value指定的值,不能註釋在字符串類型屬性上
@Min(value=) 值必須大於等於value指定的值,不能註釋在字符串類型屬性上

主要區分下@NotNull@NotEmpty@NotBlank3個註解的區別:

@NotNull 任何對象的value不能爲null

@NotEmpty 集合對象的元素不爲0,即集合不爲空,也可以用於字符串不爲null

@NotBlank 只能用於字符串不爲null,並且字符串trim()以後length要大於0

其實以上的每個註解都有三個共同的屬性,因爲他們都遵循JSR 303規範:

String message() default "{org.hibernate.validator.constraints.xxx.message}";

Class<?>[] groups() default { };

Class<? extends Payload>[] payload() default { };
  • message提供校驗失敗後的錯誤消息;

  • groups分組驗證

  • payload承載元數據

爲了測試註解的作用,我在User類的屬性上加了部分註解,如下所示:

@NotEmpty(message = "用戶名不能爲空")
private String username;

@NotEmpty(message = "密碼不能爲空")
private String password;

@Past(message = "生日必須是過去的日期")
private Date birthday;

這裏我寫一個創建用戶的測試用例和Controller,並人爲設置不符合要求的數據,測試用例代碼如下:

@Test
public void create3() throws Exception {
    Date date = new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
    String content = "{\"username\":null,\"password\":null,\"birthday\":" + date.getTime() + "}";
    mockMvc.perform(MockMvcRequestBuilders.post("/user3")
            .contentType(MediaType.APPLICATION_JSON_UTF8)
            .content(content))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(3));
}

爲了測試效果,我設置的時間不是過去的時間,而是未來一年的時間,LocalDateTime.now()獲取當前時間,plusYears(1)加上一年,atZone(ZoneId.systemDefault())設置當前時區爲系統默認時區,最後再毫秒化。
Controller方法爲:

@PostMapping("/user3")
public User create3(@RequestBody @Valid User user, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        bindingResult.getAllErrors().forEach(error -> System.out.println(error.getDefaultMessage()));
    }
    user.setId(3);
    return user;

}

這裏對錯誤消息進行了循環打印,打印結果是:

用戶名不能爲空
生日必須是過去的日期
密碼不能爲空

@Valid註解在數據封裝之間會對數據的合法性進行校驗,並將校驗的錯誤結果存儲在BindingResult對象中。

二、自定義校驗註解

以上的所有註解都是Java給我們提供的,其實他們往往只能校驗一些簡單的值,在實際開發中,我們面臨的校驗可能會很複雜,所以校驗邏輯往往需要我們自己來寫,這時候就需要我們自定義校驗註解了。接下來我以校驗身份證號碼的案例來說明如何實現自定義的校驗註解。
一般來說,自定義校驗註解的開發步驟有以下幾步:
第一步: 編寫校驗註解,但是需要注意的是,自定義的校驗註解也得和其他Java提供的校驗註解一樣,必須擁有messagegroupspayload三個屬性。
第二步: 編寫自定義校驗的邏輯實體類,這個類必須實現ConstraintValidator這個接口,這樣纔可以被註解用來校驗。
第三步: 編寫具體的校驗邏輯。

編寫註解:
package com.lemon.security.web.validator;


import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定義身份證號碼校驗註解
 *
 * @author lemon
 * @date 2018/3/31 下午7:43
 */
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = IdCardValidator.class)
public @interface IsIdCard {

    String message();

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };

}

對上面的代碼進行如下解釋:

  • messagegroupspayload三個屬性是必須的,可以參考@NotNull等註解;

  • @Target註解是指定當前自定義註解可以使用在哪些地方,這裏僅僅讓他可以使用在方法上和屬性上;

  • @Retention指定當前註解保留到運行時;

  • @Constraint指定了當前註解使用哪個類來進行校驗。

編寫註解校驗邏輯類:
package com.lemon.security.web.validator;

import com.lemon.security.web.service.IdCardValidatorService;
import org.springframework.beans.factory.annotation.Autowired;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * 校驗註解的校驗邏輯
 *
 * @author lemon
 * @date 2018/3/31 下午7:57
 */
public class IdCardValidator implements ConstraintValidator<IsIdCard, String> {

    @Autowired
    private IdCardValidatorService idCardValidatorService;

    /**
     * 校驗前的初始化工作
     *
     * @param constraintAnnotation 自定義的校驗註解
     */
    @Override
    public void initialize(IsIdCard constraintAnnotation) {
        String message = constraintAnnotation.message();
        System.out.println("用戶自定義的message信息是:".concat(message));
    }

    /**
     * 具體的校驗邏輯方法
     *
     * @param value   需要校驗的值,從前端傳遞過來
     * @param context 校驗器的校驗環境
     * @return 通過校驗返回true,否則返回false
     */
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return idCardValidatorService.valid(value);
    }
}

對以上代碼進行如下解釋:

  • 校驗身份證合法性的實體類IdCardValidator實現了接口ConstraintValidator,其後面的第一個泛型是指爲哪個註解提供校驗服務,第二個泛型是指需要校驗的值的類型;

  • 它需要實現兩個方法,第一個是初始化方法,第二個是校驗的邏輯方法,在啓動校驗方法之前,都會進行初始化,可以在初始化方法中初始一些數據,比如獲取用戶自定義message內容;第二個方法的第一個參數是需要被校驗的值,第二個參數是校驗的上下文環境;

  • 一般的開發過程中,往往將校驗邏輯抽取成爲一個Service服務,並通過SpringDI注入到這個校驗類中,需要注意的是,這個校驗類上並不需要添加SpringComponent等註解,Spring可以自動將校驗邏輯服務類實例對象注入到這個類中。在這裏就注入了IdCardValidatorService實現類對象。

編寫註解校驗邏輯接口和實現類:

接口:

package com.lemon.security.web.service;

/**
 * @author lemon
 * @date 2018/3/31 下午8:11
 */
public interface IdCardValidatorService {

    /**
     * 身份證號校驗,支持18位、15位和港澳臺的10位
     *
     * @param value 需要被校驗的值
     * @return 校驗通過返回true,否則返回false
     */
    boolean valid(String value);
}

實現類:

package com.lemon.security.web.service.impl;

import cn.hutool.core.util.IdcardUtil;
import com.lemon.security.web.service.IdCardValidatorService;
import org.springframework.stereotype.Service;

/**
 * @author lemon
 * @date 2018/3/31 下午8:14
 */
@Service
public class IdCardValidatorServiceImpl implements IdCardValidatorService {

    @Override
    public boolean valid(String value) {
        return IdcardUtil.isValidCard(value);
    }
}

這裏的校驗邏輯採用的是Hutool提供的工具包進行校驗的,具體可以參考它的文檔
這就完成了自定義校驗註解的完整案例編寫,接下來進行提供RESTful風格的API進行測試。在測試之前,請在原來的User類上加上idCard屬性,並加上@IsIdCard註解。

@IsIdCard(message = "身份證號碼必須是大陸的18位或者15位,或者是港澳臺的10位")
private String idCard;

測試方法:

@Test
public void create4() throws Exception {
    Date date = new Date(LocalDateTime.now().plusYears(1).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
    String content = "{\"username\":null,\"password\":null,\"birthday\":" + date.getTime() + ",\"idCard\":\"12345678\"}";
    mockMvc.perform(MockMvcRequestBuilders.post("/user4")
            .contentType(MediaType.APPLICATION_JSON_UTF8)
            .content(content))
            .andExpect(MockMvcResultMatchers.status().isOk())
            .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(4));
}

Controller方法:

@PostMapping("/user4")
public User create4(@RequestBody @Valid User user, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        bindingResult.getAllErrors().forEach(error -> System.out.println(error.getDefaultMessage()));
    }
    user.setId(4);
    return user;
}

在上面的測試方法中,隨便設置了一個不合法的身份證號碼,顯然是會校驗失敗的,最後的打印結果是:

用戶自定義的`message`信息是:身份證號碼必須是大陸的18位或者15位,或者是港澳臺的10位
身份證號碼必須是大陸的18位或者15位,或者是港澳臺的10位
用戶名不能爲空
生日必須是過去的日期
密碼不能爲空

請認真思考上面的一個自定義校驗註解的流程,可以輕鬆掌握在後期的開發中,使用註解來實現校驗,而不是寫許多重複的校驗邏輯代碼。

Spring Security技術棧開發企業級認證與授權系列文章列表:

Spring Security技術棧學習筆記(一)環境搭建
Spring Security技術棧學習筆記(二)RESTful API詳解
Spring Security技術棧學習筆記(三)表單校驗以及自定義校驗註解開發
Spring Security技術棧學習筆記(四)RESTful API服務異常處理
Spring Security技術棧學習筆記(五)使用Filter、Interceptor和AOP攔截REST服務
Spring Security技術棧學習筆記(六)使用REST方式處理文件服務
Spring Security技術棧學習筆記(七)使用Swagger自動生成API文檔
Spring Security技術棧學習筆記(八)Spring Security的基本運行原理與個性化登錄實現
Spring Security技術棧學習筆記(九)開發圖形驗證碼接口
Spring Security技術棧學習筆記(十)開發記住我功能
Spring Security技術棧學習筆記(十一)開發短信驗證碼登錄
Spring Security技術棧學習筆記(十二)將短信驗證碼驗證方式集成到Spring Security
Spring Security技術棧學習筆記(十三)Spring Social集成第三方登錄驗證開發流程介紹
Spring Security技術棧學習筆記(十四)使用Spring Social集成QQ登錄驗證方式
Spring Security技術棧學習筆記(十五)解決Spring Social集成QQ登錄後的註冊問題
Spring Security技術棧學習筆記(十六)使用Spring Social集成微信登錄驗證方式
示例代碼下載地址:
項目已經上傳到碼雲,歡迎下載,內容所在文件夾爲chapter003

更多幹貨分享,歡迎關注我的微信公衆號:爪哇論劍(微信號:itlemon)
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章