validator自定义校验注解及使用
官方文档:https://docs.jboss.org/hibernate/validator/8.0/reference/en-US/html_single/#validator-customconstraints
用到依赖:
<!--validator的依赖如果项目使用的springBoot的依赖可以不用再引入
hibernate-validator 因为SpringBoot中封装了validator直接使用就可以-->
<dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>8.0.0.Alpha3</version>
</dependency>
<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.3.1</version>
</dependency>
可以分为四种使用方式:
1简单自定义注解约束:
校验手机号注解
/*** 检验手机号注解* @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE}):* 定义约束支持的目标元素类型。@CheckCase* 可用于字段(元素类型FIELD)、* JavaBeans* 属性以及方法返回值(METHOD)、* 方法/构造函数参数(PARAMETER)* 和参数化类型的类型参数(TYPE_USE)。* 元素类型ANNOTATION_TYPE允许基于* .@CheckCase* 创建类级别约束时(参见第 2.1.4 节,“类级别约束”TYPE ),* 必须使用元素类型。针对构造函数返回值的约束需要支持元素类型CONSTRUCTOR。* 用于同时验证方法或构造函数的所有参数的交叉参数约束(参见 第 6.3 节,“交叉参数约束”METHOD )必须分别支持or CONSTRUCTOR。** @Retention(RUNTIME):指定,这种类型的注解将在运行时通过反射的方式可用** @Constraint(validatedBy = CheckCaseValidator.class): 将注解类型标记为约束注解,并指定用于验证用 注释的元素的验证器@CheckCase。* 如果一个约束可以用于多种数据类型,则可以指定多个验证器,每个数据类型一个。** @Documented:说,使用@CheckCase将包含在用它注释的元素的JavaDoc中** @Repeatable(List.class): 表示注解可以在同一个地方重复多次,通常使用不同的配置。List是包含注释类型。** @author ****/
@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE, TYPE_USE })
@Retention(RUNTIME)
@Constraint(validatedBy = CheckPhoneValidator.class)
@Documented
@Repeatable(List.class)
public @interface CheckPhone {/*** 默认提示信息* @return*/String message() default "默认的提示!!";/***分组使用* @return*/Class<?>[] groups() default { };/*** 在ValidatorFactory初始化期间定义约束验证器有效负载* @return*/Class<? extends Payload>[] payload() default { };/*** 指定使用什么逻辑校验手机号* @return*/PhoneModeEnum value();@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })@Retention(RUNTIME)@Documented@interface List {CheckPhone[] value();}
}
调用校验手机号逻辑
/*** 自定义校验逻辑* @author *****/
public class CheckPhoneValidator implements ConstraintValidator<CheckPhone, String> {private PhoneModeEnum phoneMode;/*** initialize()方法使您可以访问已验证约束的属性值,并允许您将它们存储在验证器的字段中* @param constraintAnnotation*/@Overridepublic void initialize(CheckPhone constraintAnnotation) {this.phoneMode = constraintAnnotation.value();}/*** isValid()方法包含实际的验证逻辑* @param mobile* @param constraintContext* @return*/@Overridepublic boolean isValid(String mobile, ConstraintValidatorContext constraintContext) {if (ObjectUtil.isNull(mobile)) {return true;}if (ObjectUtil.equal(phoneMode ,PhoneModeEnum.IS_MOBILE_HK)) {return PhoneUtil.isMobileHk(mobile);} else if ((ObjectUtil.equal(phoneMode ,PhoneModeEnum.IS_MOBILE_TW))){return PhoneUtil.isMobileTw(mobile);} else if ((ObjectUtil.equal(phoneMode ,PhoneModeEnum.IS_MOBILE_MO))){return PhoneUtil.isMobileMo(mobile);} else if ((ObjectUtil.equal(phoneMode ,PhoneModeEnum.IS_MOBILE))){return PhoneUtil.isMobile(mobile);} else {return PhoneUtil.isPhone(mobile);}}
}
指定使用什么逻辑校验手机号
/***指定校验逻辑使用* @author ****/public enum PhoneModeEnum {/*** 香港手机号*/IS_MOBILE_HK,/*** 台湾手机号*/IS_MOBILE_TW,/*** 澳门手机号*/IS_MOBILE_MO,/*** 大陆手机号*/IS_MOBILE,/*** 中国手机号*/IS_PHONE;
}
手机号校验逻辑
/*** 电话号码工具类,* 借鉴hutool工具类中的** @author ****/
public class PhoneUtil {/*** 中国大陆移动电话* eg: 中国大陆: +86 180 4953 1399,2位区域码标示+13位数字* 中国大陆 +86 Mainland China*/public final static Pattern MOBILE = Pattern.compile("(?:0|86|\\+86)?1[3456789]\\d{9}");/*** 中国香港移动电话* eg: 中国香港: +852 5100 4810, 三位区域码+10位数字, 中国香港手机号码8位数* eg: 中国大陆: +86 180 4953 1399,2位区域码标示+13位数字* 中国大陆 +86 Mainland China* 中国香港 +852 Hong Kong* 中国澳门 +853 Macao* 中国台湾 +886 Taiwan*/public final static Pattern MOBILE_HK = Pattern.compile("(?:0|852|\\+852)?\\d{8}");/*** 中国台湾移动电话* eg: 中国台湾: +886 09 60 000000, 三位区域码+号码以数字09开头 + 8位数字, 中国台湾手机号码10位数* 中国台湾 +886 Taiwan 国际域名缩写:TW*/public final static Pattern MOBILE_TW = Pattern.compile("(?:0|886|\\+886)?(?:|-)09\\d{8}");/*** 中国澳门移动电话* eg: 中国台湾: +853 68 00000, 三位区域码 +号码以数字6开头 + 7位数字, 中国台湾手机号码8位数* 中国澳门 +853 Macao 国际域名缩写:MO*/public final static Pattern MOBILE_MO = Pattern.compile("(?:0|853|\\+853)?(?:|-)6\\d{7}");/*** 验证是否为中国大陆(大陆)** @param value 值* @return 是否为手机号码(大陆)* @since 5.3.11*/public static boolean isMobile(CharSequence value) {return isMatchRegex(MOBILE, value);}/*** 验证是否为手机号码(香港)** @param value 手机号码* @return 是否为香港手机号码* @author dazer, ourslook* @since 5.6.3*/public static boolean isMobileHk(CharSequence value) {return isMatchRegex(MOBILE_HK, value);}/*** 验证是否为手机号码(台湾)** @param value 手机号码* @return 是否为台湾手机号码* @author ihao* @since 5.6.6*/public static boolean isMobileTw(CharSequence value) {return isMatchRegex(MOBILE_TW, value);}/*** 验证是否为手机号码(澳门)** @param value 手机号码* @return 是否为澳门手机号码* @author ihao* @since 5.6.6*/public static boolean isMobileMo(CharSequence value) {return isMatchRegex(MOBILE_MO, value);}/*** 验证是否为手机号码(中国)** @param value 值* @return 手机号码(大陆)+手机号码(香港)+手机号码(台湾)+手机号码(澳门)* @since 5.3.11*/public static boolean isPhone(CharSequence value) {return isMobile(value) || isMobileHk(value) || isMobileTw(value) || isMobileMo(value);}/*** 通过正则表达式验证** @param pattern 正则模式* @param value 值* @return 是否匹配正则*/public static boolean isMatchRegex(Pattern pattern, CharSequence value) {return isMatch(pattern, value);}/*** 给定内容是否匹配正则** @param pattern 模式* @param content 内容* @return 正则为null或者""则不检查,返回true,内容为null返回false*/public static boolean isMatch(Pattern pattern, CharSequence content) {if (content == null || pattern == null) {// 提供null的字符串为不匹配return false;}return pattern.matcher(content).matches();}
}
使用案例
/***用户信息* @author ****/
public class UserInfo implements Serializable {/*** 使用注解指定这个字段需要校验* value 指定使用大陆校验方式* message 自定义提示信息 * ${validatedValue} 代表返回信息携带请求参数 * 提示信息 : 123132 手机号格式异常*/@CheckPhone(value = PhoneModeEnum.IS_PHONE,message = "${validatedValue}"+"手机号码格式异常!")private String mobile;}
使用main方法测试校验
/** * 测试自定义手机校验* @author *****/
@Slf4j
public class TestValidationPhone {public static void main(String[] args) {ValidatorFactory factory = Validation.buildDefaultValidatorFactory();Validator validator = factory.getValidator();UserInfo userInfo = new UserInfo();userInfo.setMobile("123123");Set<ConstraintViolation<UserInfo>> checkInfo = validator.validate(userInfo);System.out.println(checkInfo);}}
返回参数如下
使用postman测试校验
/***测试类* @author *****/
@Slf4j
@RestController
@RequestMapping("/demo")
public class TestValidationController {/*** 测试校验手机号* @param userInfo*/@PostMapping("/test/phone")public void TestValidationPhone (@Valid @RequestBody UserInfo userInfo){return;}}
postman校验返回请求参数和返回参数
2简单自定义类注解约束:(作用到类上面的注解)
校验汽车超载的注解:
/*** 校验汽车类人数是否超载注解** @Target(ElementType.TYPE)//接口、类、枚举、注解* @Target(ElementType.FIELD)//字段、枚举的常量* @Target(ElementType.METHOD)//方法* @Target(ElementType.PARAMETER)//方法参数* @Target(ElementType.CONSTRUCTOR) //构造函数* @Target(ElementType.LOCAL_VARIABLE)//局部变量* @Target(ElementType.ANNOTATION_TYPE)//注解* @Target(ElementType.PACKAGE)//包* */
@Target({ TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = { ValidPassengerCountValidator.class })
@Documented
public @interface ValidPassengerCount {/*** 提示信息* @return*/String message() default "默认提醒信息";/*** 分组使用* @return*/Class<?>[] groups() default { };/***在ValidatorFactory初始化期间定义约束验证器有效负载* @return*/Class<? extends Payload>[] payload() default { };
}
校验逻辑:
/*** 校验汽车类人数是否超载逻辑*/
public class ValidPassengerCountValidator implements ConstraintValidator<ValidPassengerCount, Car> {@Overridepublic void initialize(ValidPassengerCount constraintAnnotation) {}@Overridepublic boolean isValid(Car car, ConstraintValidatorContext context) {if (car == null) {return true;}return car.getPassengers().size() <= car.getSeatCount();}
}
乘客类
/*** 乘客类* @author ****/
public class Person {private String name;public Person(String name) {this.name = name;}}
汽车类:
/*** 汽车类* @author ****/
@ValidPassengerCount(message = "乘客数不得超过座位数.")
public class Car {private int seatCount;private List<Person> passengers;public Car(int seatCount, List<Person> passengers) {this.seatCount = seatCount;this.passengers = passengers;}public int getSeatCount() {return seatCount;}public List<Person> getPassengers() {return passengers;}
}
main方法测试类:
/*** 测试自定义校验* @author *****/
@Slf4j
public class TestValidationCar {public static void main(String[] args) {ValidatorFactory factory = Validation.buildDefaultValidatorFactory();Validator validator = factory.getValidator();Person person1 = new Person("小明");Person person2 = new Person("小红");Person person3 = new Person("小李");Person person4 = new Person("小王");Person person5 = new Person("小爱");ArrayList<Person> people = new ArrayList<>();people.add(person1);people.add(person2);people.add(person3);people.add(person4);people.add(person5);Car car = new Car(4,people);Set<ConstraintViolation<Car>> validate = validator.validate(car);System.out.println(validate);}}
3跨参数约束:
跨参数约束适用于方法的参数数组或构造函数,可用于表达依赖于多个参数值的验证逻辑。
校验两个时间字段是否第一个时间是在后一个时间之前的注解:
/*** 多参数校验只适用于* 方法上面* 构造函数* 注解**/
@Constraint(validatedBy = ConsistentDateParametersValidator.class)
@Target({ METHOD, CONSTRUCTOR, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Documented
public @interface ConsistentDateParameters {String message() default "多参数校验!!!";Class<?>[] groups() default { };Class<? extends Payload>[] payload() default { };
}
/**** @SupportedValidationTarget(ValidationTarget.PARAMETERS)表示这个类是多参数校验使用 * 多参数校验逻辑 * */
@SupportedValidationTarget(ValidationTarget.PARAMETERS)
public class ConsistentDateParametersValidator implements ConstraintValidator<ConsistentDateParameters, Object[]> {@Overridepublic void initialize(ConsistentDateParameters constraintAnnotation) {}@Overridepublic boolean isValid(Object[] value, ConstraintValidatorContext context) {if (value.length != 2) {throw new IllegalArgumentException("Illegal method signature");}//leave null-checking to @NotNull on individual parametersif (value[0] == null || value[1] == null) {return true;}if (!(value[0] instanceof Date) || !(value[1] instanceof Date)) {throw new IllegalArgumentException("Illegal method signature, expected two " +"parameters of type Date.");}return ((Date) value[0]).before((Date) value[1]);}
}
多参数校验main方法
/*** 测试自定义多参数校验* @author *****/
public class TestValidationParams {public static void main(String[] args) {ValidatorFactory factory = Validation.buildDefaultValidatorFactory();//使用多参数校验的类ExecutableValidator executableValidator = factory.getValidator().forExecutables();CalendarService object = new CalendarService();Method method = null;try {method = CalendarService.class.getMethod( "createCalendarEvent", Date.class, Date.class );} catch (NoSuchMethodException e) {e.printStackTrace();}Calendar start = Calendar.getInstance();start.set( 2013, 5, 10 );Calendar end = Calendar.getInstance();end.set( 2013, 5, 9 );Object[] parameterValues = new Object[] { start.getTime(), end.getTime() };Set<ConstraintViolation<CalendarService>> constraintViolations = executableValidator.validateParameters(object, method, parameterValues);System.out.println(constraintViolations);}private static class CalendarService {@ConsistentDateParameters(message = "开始时间比结束时间大")public void createCalendarEvent(@NotNull Date start, @NotNull Date end) {//.......}}}
4约束组合
当一个字段要进行多种校验的时候可以将多种校验组合到一个注解上 进行校验 ;
汽车类:
/*** 汽车类* @author ****/
@ValidPassengerCount(message = "乘客数不得超过座位数.")
public class Car {/***座位数*/private int seatCount;/*** 牌照*约束组合就是一个注解替换下面三个注解*/@NotNull@Size(min = 2, max = 14)@CheckCase(value = CaseMode.UPPER,message = "牌照字母不是大写的!!")private String licensePlate;/*** 乘客*/private List<Person> passengers;public Car(int seatCount, List<Person> passengers) {this.seatCount = seatCount;this.passengers = passengers;}public Car(int seatCount, String licensePlate, List<Person> passengers) {this.seatCount = seatCount;this.licensePlate = licensePlate;this.passengers = passengers;}public int getSeatCount() {return seatCount;}public List<Person> getPassengers() {return passengers;}public String getLicensePlate() {return licensePlate;}public void setLicensePlate(String licensePlate) {this.licensePlate = licensePlate;}
}
乘客类
/*** 乘客类* @author ****/
public class Person {private String name;public Person(String name) {this.name = name;}}
校验字母格式注解
/*** 校验大小写注解*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
@Documented
public @interface CheckCase {String message() default "";Class<?>[] groups() default { };Class<? extends Payload>[] payload() default { };CaseMode value();
}
枚举类:
/*** 用枚举表示大写和小写* @author ****/
public enum CaseMode {/*** 大写*/UPPER,/*** 小写*/LOWER;}
校验逻辑:
/*** 校验大小写逻辑* @author *****/
public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {private CaseMode caseMode;@Overridepublic void initialize(CheckCase constraintAnnotation) {this.caseMode = constraintAnnotation.value();}@Overridepublic boolean isValid(String object, ConstraintValidatorContext constraintContext) {if ( object == null ) {return true;}if ( caseMode == CaseMode.UPPER ) {return object.equals( object.toUpperCase() );}else {return object.equals( object.toLowerCase() );}}
}
校验测试类
/*** 测试自定义校验* @author *****/
public class TestValidationCar {public static void main(String[] args) {ValidatorFactory factory = Validation.buildDefaultValidatorFactory();Validator validator = factory.getValidator();Person person1 = new Person("小明");Person person2 = new Person("小红");Person person3 = new Person("小李");Person person4 = new Person("小王");Person person5 = new Person("小爱");ArrayList<Person> people = new ArrayList<>();people.add(person1);people.add(person2);people.add(person3);people.add(person4);people.add(person5);Car car = new Car(4,"123asd",people);Set<ConstraintViolation<Car>> validate = validator.validate(car);System.out.println(validate);}}
校验结果:
组合约束:
/*** 一个@ValidLicensePlate注解替换掉下面三个注解* @NotNull* @Size* @CheckCase*/
@NotNull
@Size(min = 2, max = 14)
@CheckCase(value = CaseMode.UPPER,message = "车牌字母必须全部是大写")
@Target({ METHOD, FIELD, ANNOTATION_TYPE, TYPE_USE })
@Retention(RUNTIME)
@Constraint(validatedBy = { })
@Documented
public @interface ValidLicensePlate {String message() default "{org.hibernate.validator.referenceguide.chapter06." +"constraintcomposition.ValidLicensePlate.message}";Class<?>[] groups() default { };Class<? extends Payload>[] payload() default { };
}
使用:
/*** 汽车类* @author ****/
@ValidPassengerCount(message = "乘客数不得超过座位数.")
public class Car {/***座位数*/private int seatCount;/*** 牌照*/@ValidLicensePlateprivate String licensePlate;/*** 乘客*/private List<Person> passengers;public Car(int seatCount, List<Person> passengers) {this.seatCount = seatCount;this.passengers = passengers;}public Car(int seatCount, String licensePlate, List<Person> passengers) {this.seatCount = seatCount;this.licensePlate = licensePlate;this.passengers = passengers;}public int getSeatCount() {return seatCount;}public List<Person> getPassengers() {return passengers;}public String getLicensePlate() {return licensePlate;}public void setLicensePlate(String licensePlate) {this.licensePlate = licensePlate;}
}
main方法校验:
/*** 测试自定义校验* @author *****/
public class TestValidationCar {public static void main(String[] args) {ValidatorFactory factory = Validation.buildDefaultValidatorFactory();Validator validator = factory.getValidator();Person person1 = new Person("小明");Person person2 = new Person("小红");Person person3 = new Person("小李");Person person4 = new Person("小王");Person person5 = new Person("小爱");ArrayList<Person> people = new ArrayList<>();people.add(person1);people.add(person2);people.add(person3);people.add(person4);people.add(person5);Car car = new Car(4,"123asd",people);Set<ConstraintViolation<Car>> validate = validator.validate(car);System.out.println(validate);}}