springboot整合jsr303實(shí)現(xiàn)參數(shù)校驗(yàn)

前言

我們?cè)趈ava項(xiàng)目開發(fā)中(典型場(chǎng)景為springmvc項(xiàng)目的controller參數(shù)接收)都需要對(duì)參數(shù)合法性(非空性,范圍,格式等)進(jìn)行校驗(yàn),最簡(jiǎn)單的寫法是使用if...else進(jìn)行逐個(gè)判斷,但是此類開發(fā)工作繁瑣,代碼不夠優(yōu)雅,這里我們可以使用jsr-303中的一項(xiàng)子規(guī)范Bean Validation來處理,其中Hibernate Validator 是 Bean Validation 的參考實(shí)現(xiàn),本次演示便是對(duì)Hibernate Validator的使用和擴(kuò)展!

一僚碎、代碼清單

  • Example.java 自定義擴(kuò)展校驗(yàn)注解及Hibernate Validator標(biāo)準(zhǔn)注解使用示例類
  • InEnumValues.java 自定義擴(kuò)展校驗(yàn)注解類
  • InEnumValuesValidatorImpl.java 自定義擴(kuò)展校驗(yàn)實(shí)現(xiàn)類
  • ValidateUtil.java 核心校驗(yàn)工具類

二侣签、具體實(shí)現(xiàn)

package cn.seally.collector.validator;

import javax.validation.constraints.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

/**
 * @Description 參數(shù)校驗(yàn)使用示例類,具體使用參照該類的main方法肌幽,可用注解查閱ValidateUtil類的說明
 * @Date 2021/11/30 01:16
 * @Author dengningcheng
 **/
public class Example {

    @NotNull
    private String name;

    @NotEmpty
    private String gender;
    
    @AssertTrue
    @NotNull
    private Boolean woman;

    @Pattern(regexp="^1\\d{10}$")
    @NotNull
    private String telephone;

    @Min(1)
    @Max(100)
    @NotNull
    private Integer age;
    
    @Size(min = 1,max = 2)
    private String address;

    @Digits(integer = 3,fraction = 2)
    @NotNull
    private Double weight;

    private Long foot;

    private Date birth;

    @DecimalMin("0.00")
    @DecimalMax("2.00")
    @NotNull
    private BigDecimal money;

    private Float salary;
    
    @InEnumValues(strEnums = {"ellie","dennis"})
    @NotNull
    private String friend;

    @InEnumValues(intEnums = {1,2,3})
    private Integer level;

    @NotEmpty
    @Size(min=1,max = 2,message = "個(gè)數(shù)超出期望值范圍")
    private List<String> hobbies;

    private Set<Integer> friends;

    public static void main(String[] args) {
        
        Example param = new Example();
        param.setWoman(false);
        param.setAddress("你好么");
        param.setMoney(new BigDecimal("-12.0"));
        ArrayList<String> hobbies = new ArrayList<>();hobbies.add("打球");hobbies.add("看書");hobbies.add("看電影");
        param.setHobbies(hobbies);
        param.setWeight(Double.valueOf(123.234D));
        param.setTelephone("130");
        param.setFriend("dnc");
        param.setLevel(4);
        
        System.out.println("param校驗(yàn)所有屬性結(jié)果為:\n"+ValidateUtil.validate(param));
        System.out.println("param校驗(yàn)指定屬性結(jié)果為:\n"+ValidateUtil.validate(param,"name"));

    }

    //此處忽略演示類的setters淹冰、getters

}
package cn.seally.collector.validator;

import cn.seally.collector.common.ApiException;
import org.apache.commons.lang3.StringUtils;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.*;

/**
 * @Description 參數(shù)校驗(yàn)工具類
 *
 * 該類是基于 Hibernate Validator 構(gòu)建
 * 而Hibernate Validator是 基于JSR-303標(biāo)準(zhǔn)的 validation bean 的實(shí)現(xiàn)之一
 * Springboot默認(rèn)集成了Hibernate Validator,如果是非Springboot項(xiàng)目則需要pom主動(dòng)添加以下依賴
 *
 * <dependency>
 *   <groupId>javax.validation</groupId>
 *   <artifactId>validation-api</artifactId>
 *   <version>2.0.1.Final</version>
 * </dependency>
 *
 * 一、原始標(biāo)準(zhǔn)校驗(yàn)注解列表:
 * AssertFalse,AssertTrue,DecimalMax,DecimalMin,Digits,Email,Future,FutureOrPresent,Max,Min,Negative,NegativeOrZero,NotBlank,NotEmpty,NotNull,Null,Past,PastOrPresent,Pattern,Positive,PositiveOrZero,Size
 *
 * 二、自定義擴(kuò)展校驗(yàn)注解列表:
 * InEnumValues
 *
 * @Date 2021/11/30 00:17
 * @Author dengningcheng
 **/
public class ValidateUtil {

    private static final Validator validator = Validation.buildDefaultValidatorFactory().getValidator();

    /**
     * @Description 對(duì)象屬性值校驗(yàn)方法
     * @param t 需要驗(yàn)證的對(duì)象
     * @param fields 需要驗(yàn)證的字段竹习,如果不指定則校驗(yàn)所有注解字段,如果傳遞則只校驗(yàn)指定的字段
     * @Date 2021/11/30 01:08
     * @Author dengningcheng
     **/
    public static  <T> String validate(T t,String... fields) throws ApiException {
        if (null == t) {
            return "校驗(yàn)對(duì)象為空";
        }
        List<String> errorList = new ArrayList<>();
        Set<ConstraintViolation<T>> constraintViolations = new HashSet<>();
        boolean checkALL = true;
        if(null != fields && fields.length > 0){
            for(String field: fields){
                if(null != field && !field.trim().isEmpty()){
                    checkALL = false;
                    constraintViolations.addAll(validator.validateProperty(t,field));
                }
            }
        }
        if(checkALL){
            constraintViolations = validator.validate(t);
        }

        for (ConstraintViolation<T> constraintViolation : constraintViolations) {
            errorList.add("["+constraintViolation.getPropertyPath()+"]"+constraintViolation.getMessage());
        }
        if ((null != errorList) && !errorList.isEmpty()) {
            return StringUtils.join(errorList, ";");
        }
        return null;
    }

}
package cn.seally.collector.validator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
/**
 * @Description 自定義校驗(yàn)注解:檢測(cè)屬性是否在列舉的枚舉值之列
 * @Date 2021/12/1 00:25
 * @Author dengningcheng
**/
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = InEnumValuesValidatorImpl.class)//此處指定了注解的實(shí)現(xiàn)類為InEnumValuesValidatorImpl
public @interface InEnumValues {
    /**
     * @Description 1列牺、自定義該校驗(yàn)注解需要的注解屬性由驹,用于使用該注解到字段上時(shí),傳入注解元信息昔园,到時(shí)候會(huì)在注解校驗(yàn)實(shí)現(xiàn)類中用到,可以自定義類型和數(shù)量
     * @Date 2021/12/1 00:52
     * @Author dengningcheng
    **/
    int[] intEnums() default {};
    String[] strEnums() default {};

    /**
     * @Description 2并炮、以下為擴(kuò)展校驗(yàn)的標(biāo)準(zhǔn)屬性默刚,擴(kuò)展時(shí)原樣寫,動(dòng)態(tài)修改message的默認(rèn)提示語即可
     * @Date 2021/12/1 00:52
     * @Author dengningcheng
    **/
    String message() default "不在指定枚舉值范圍";

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

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

    /**
     * @Description 3逃魄、定義List,為了讓Bean的一個(gè)屬性上可以添加多套規(guī)則,自定義時(shí)可照寫僅僅修改該注解的屬性類型如此處InEnumValues
     * @Date 2021/12/1 00:52
     * @Author dengningcheng
    **/
    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @interface List {
        InEnumValues[] value();
    }
}
package cn.seally.collector.validator;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;

/**
 * @Description 自定義校驗(yàn)處理最終處理類荤西,必須要實(shí)現(xiàn)ConstraintValidator接口,范型為 <注解類,校驗(yàn)時(shí)傳入的檢測(cè)目標(biāo)值類>
 * @Date 2021/12/1 00:21
 * @Author dengningcheng
 **/
public class InEnumValuesValidatorImpl implements ConstraintValidator<InEnumValues, Object> {
    /**
     * @Description 1伍俘、這里添加自己校驗(yàn)實(shí)現(xiàn)方法isValid中需要用到的一些輔助字段邪锌,可以時(shí)注解中的屬性,也可以是其他類型數(shù)據(jù)癌瘾,根據(jù)校驗(yàn)需要而定觅丰,這些屬性的初始化可以在initialize方法的入?yún)⒆⒔饽玫絹泶鎯?chǔ)或是組裝(也就是拿到最終使用時(shí)注解到屬性上面的注解信息)
     * @Date 2021/12/1 00:40
     * @Author dengningcheng
    **/    
    Set<String> values = new HashSet<>();//這里我本意是想要校驗(yàn)基礎(chǔ)常用類型比如整型和字符串行的枚舉值,但是整型和字符串型都可以轉(zhuǎn)為字符串統(tǒng)一判斷妨退,因此定義一個(gè)字符串型的Set<String>來盛裝注解中的intEnums和strEnums中獲取到的校驗(yàn)比對(duì)范圍
    
    /**
     * @Description 2妇萄、重寫該方法,校驗(yàn)時(shí)會(huì)傳遞進(jìn)來校驗(yàn)字段的注解咬荷,通過注解獲取到我們校驗(yàn)時(shí)字段注解上的一些元信息冠句,我們用此元信息來初始化幫助我們?cè)趇sValid進(jìn)行比對(duì)判斷的values
     * @Date 2021/12/1 00:39
     * @Author dengningcheng
    **/
    @Override
    public void initialize(InEnumValues constraintAnnotation) {
        if(null != constraintAnnotation.intEnums()){
            for(int i : constraintAnnotation.intEnums()){
                values.add(i+"");
            }
        }
        if(null != constraintAnnotation.strEnums()){
            for(String i : constraintAnnotation.strEnums()){
                values.add(i);
            }
        }
    }

    /**
     * @Description 3、重寫該方法幸乒,實(shí)現(xiàn)自己真正的校驗(yàn)邏輯
     * @Date 2021/12/1 00:42
     * @Author dengningcheng
    **/
    @Override
    public boolean isValid(Object object, ConstraintValidatorContext constraintValidatorContext) {
        //如果校驗(yàn)值是null認(rèn)為校驗(yàn)通過懦底,因?yàn)橛袑iT的非null校驗(yàn)注解去做非null的校驗(yàn)
        if(null == object){
            return true;
        }
        //如果該注解包含需要校驗(yàn)的
        if(null != values && !values.isEmpty()){
            return values.contains(object.toString());
        }
        return true;
    }
}

以一種更為簡(jiǎn)單的方式進(jìn)行點(diǎn)滴分享...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市罕扎,隨后出現(xiàn)的幾起案子聚唐,更是在濱河造成了極大的恐慌丐重,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拱层,死亡現(xiàn)場(chǎng)離奇詭異弥臼,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)根灯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門径缅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人烙肺,你說我怎么就攤上這事纳猪。” “怎么了桃笙?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵氏堤,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我搏明,道長(zhǎng)鼠锈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任星著,我火速辦了婚禮购笆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘虚循。我一直安慰自己同欠,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布横缔。 她就那樣靜靜地躺著,像睡著了一般茎刚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上膛锭,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音泉沾,去河邊找鬼捞蚂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛姓迅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播丁存,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼肩杈,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了解寝?” 一聲冷哼從身側(cè)響起扩然,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤聋伦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后觉增,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡说铃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年嘹履,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片砾嫉。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出舌剂,到底是詐尸還是另有隱情,我是刑警寧澤霍转,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站低滩,受9級(jí)特大地震影響岩喷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜纱意,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望迄委。 院中可真熱鬧褐筛,春花似錦、人聲如沸叙身。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至愧旦,卻和暖如春定罢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背祖凫。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留遭庶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓峦睡,卻偏偏與公主長(zhǎng)得像权埠,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子攘蔽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容