springboot整合jsr303实现参数校验

前言

我们在java项目开发中(典型场景为springmvc项目的controller参数接收)都需要对参数合法性(非空性,范围,格式等)进行校验,最简单的写法是使用if...else进行逐个判断,但是此类开发工作繁琐,代码不够优雅,这里我们可以使用jsr-303中的一项子规范Bean Validation来处理,其中Hibernate Validator 是 Bean Validation 的参考实现,本次演示便是对Hibernate Validator的使用和扩展!

一、代码清单

  • Example.java 自定义扩展校验注解及Hibernate Validator标准注解使用示例类
  • InEnumValues.java 自定义扩展校验注解类
  • InEnumValuesValidatorImpl.java 自定义扩展校验实现类
  • ValidateUtil.java 核心校验工具类

二、具体实现

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 参数校验使用示例类,具体使用参照该类的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 = "个数超出期望值范围")
    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校验所有属性结果为:\n"+ValidateUtil.validate(param));
        System.out.println("param校验指定属性结果为:\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 参数校验工具类
 *
 * 该类是基于 Hibernate Validator 构建
 * 而Hibernate Validator是 基于JSR-303标准的 validation bean 的实现之一
 * Springboot默认集成了Hibernate Validator,如果是非Springboot项目则需要pom主动添加以下依赖
 *
 * <dependency>
 *   <groupId>javax.validation</groupId>
 *   <artifactId>validation-api</artifactId>
 *   <version>2.0.1.Final</version>
 * </dependency>
 *
 * 一、原始标准校验注解列表:
 * AssertFalse,AssertTrue,DecimalMax,DecimalMin,Digits,Email,Future,FutureOrPresent,Max,Min,Negative,NegativeOrZero,NotBlank,NotEmpty,NotNull,Null,Past,PastOrPresent,Pattern,Positive,PositiveOrZero,Size
 *
 * 二、自定义扩展校验注解列表:
 * InEnumValues
 *
 * @Date 2021/11/30 00:17
 * @Author dengningcheng
 **/
public class ValidateUtil {

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

    /**
     * @Description 对象属性值校验方法
     * @param t 需要验证的对象
     * @param fields 需要验证的字段,如果不指定则校验所有注解字段,如果传递则只校验指定的字段
     * @Date 2021/11/30 01:08
     * @Author dengningcheng
     **/
    public static  <T> String validate(T t,String... fields) throws ApiException {
        if (null == t) {
            return "校验对象为空";
        }
        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 自定义校验注解:检测属性是否在列举的枚举值之列
 * @Date 2021/12/1 00:25
 * @Author dengningcheng
**/
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = InEnumValuesValidatorImpl.class)//此处指定了注解的实现类为InEnumValuesValidatorImpl
public @interface InEnumValues {
    /**
     * @Description 1、自定义该校验注解需要的注解属性,用于使用该注解到字段上时,传入注解元信息,到时候会在注解校验实现类中用到,可以自定义类型和数量
     * @Date 2021/12/1 00:52
     * @Author dengningcheng
    **/
    int[] intEnums() default {};
    String[] strEnums() default {};

    /**
     * @Description 2、以下为扩展校验的标准属性,扩展时原样写,动态修改message的默认提示语即可
     * @Date 2021/12/1 00:52
     * @Author dengningcheng
    **/
    String message() default "不在指定枚举值范围";

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

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

    /**
     * @Description 3、定义List,为了让Bean的一个属性上可以添加多套规则,自定义时可照写仅仅修改该注解的属性类型如此处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 自定义校验处理最终处理类,必须要实现ConstraintValidator接口,范型为 <注解类,校验时传入的检测目标值类>
 * @Date 2021/12/1 00:21
 * @Author dengningcheng
 **/
public class InEnumValuesValidatorImpl implements ConstraintValidator<InEnumValues, Object> {
    /**
     * @Description 1、这里添加自己校验实现方法isValid中需要用到的一些辅助字段,可以时注解中的属性,也可以是其他类型数据,根据校验需要而定,这些属性的初始化可以在initialize方法的入参注解拿到来存储或是组装(也就是拿到最终使用时注解到属性上面的注解信息)
     * @Date 2021/12/1 00:40
     * @Author dengningcheng
    **/    
    Set<String> values = new HashSet<>();//这里我本意是想要校验基础常用类型比如整型和字符串行的枚举值,但是整型和字符串型都可以转为字符串统一判断,因此定义一个字符串型的Set<String>来盛装注解中的intEnums和strEnums中获取到的校验比对范围
    
    /**
     * @Description 2、重写该方法,校验时会传递进来校验字段的注解,通过注解获取到我们校验时字段注解上的一些元信息,我们用此元信息来初始化帮助我们在isValid进行比对判断的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、重写该方法,实现自己真正的校验逻辑
     * @Date 2021/12/1 00:42
     * @Author dengningcheng
    **/
    @Override
    public boolean isValid(Object object, ConstraintValidatorContext constraintValidatorContext) {
        //如果校验值是null认为校验通过,因为有专门的非null校验注解去做非null的校验
        if(null == object){
            return true;
        }
        //如果该注解包含需要校验的
        if(null != values && !values.isEmpty()){
            return values.contains(object.toString());
        }
        return true;
    }
}

以一种更为简单的方式进行点滴分享...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,290评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,107评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,872评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,415评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,453评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,784评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,927评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,691评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,137评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,472评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,622评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,289评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,887评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,316评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,490评论 2 348

推荐阅读更多精彩内容