前言
我们在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;
}
}
以一种更为简单的方式进行点滴分享...