参考在开发JAVA服务器端代码时,我们会遇到对外部传来的参数合法性进行验证,而hibernate-validator提供了一些常用的参数校验注解,我们可以拿来使用。
spring-boot-web,内嵌了hibernate-validator,并且hibernate-validator依赖tomcat-el包。在使用webflux容器下,会报错。将 hibernate-validator 改为 spring-boot-starter-validation,其中有jakarta.el替代实现el
JAVA中通过Hibernate-Validation进行参数验证
Validation校验框架的自定义注解
Spring WebFlux - 使用@Valid校验
pom引入jar
spring-boot-starter-parent org.springframework.boot 2.0.9.RELEASE org.springframework.boot spring-boot-starter-web org.hibernate.validator hibernate-validator
配置类,快速失败模式
import org.hibernate.validator.HibernateValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
@Configuration
public class ValidationCfg {
private static final Logger log = LoggerFactory.getLogger(ValidationCfg.class);
@Bean
public Validator validator(){
// 如果存在 自定义注解校验器,请 增加入参 AutowireCapableBeanFactory autowireCapableBeanFactory
// 并增加调用
// .failFast( true ) .constraintValidatorFactory(new SpringConstraintValidatorFactory(autowireCapableBeanFactory))
log.info("spring validator 快速失败模式");
try(ValidatorFactory vf = Validation.byProvider(HibernateValidator.class)
.configure().failFast(true)
.buildValidatorFactory()){
return vf.getValidator();
}
}
}
实体类dto字段使用校验注解
使用方式
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import org.hibernate.validator.HibernateValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Resource;
public class ValidationUtil{
private static final Logger logger = LoggerFactory.getLogger(ValidationUtil.class);
@Resource
private Validator validator;
public void validateBean(T t){
logger.debug("使用HibernateValidator校验实体类, {}",t);
Set> validate = validator.validate(t);
if(!validate.isEmpty()){
List list = validate.stream().map(ConstraintViolation::getMessage)
.collect(Collectors.toList());
logger.debug("使用HibernateValidator校验实体类-, {}",list);
throw new RuntimeException(list.get(0));
}
}
private static Validator validate = Validation.byProvider(HibernateValidator.class).configure()
.failFast(true).buildValidatorFactory().getValidator();
public static void validate(T obj) {
Set> constraintViolations = validate.validate(obj);
// 抛出检验异常
if (constraintViolations.size() > 0) {
throw new RuntimeException(String.format("0001参数校验失败:%s",
constraintViolations.iterator().next().getMessage()));
}
}
}
自定义校验注解
validation框架的自定义注解实现必要容易
- 自定义一个注解类
- 重写校验器
参考别人的自定义代码时候,发现使用反射方式+方法字符串,执行验证方法。就考虑怎么固定方法并且不适用反射方式。通过接口方式就实现了。
public interface AbsValid {
boolean eq(String val);
String key();
}
自定义校验注解
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;
@Constraint (validatedBy = EnumValidator.class)
@Retention (RetentionPolicy.RUNTIME)
@Target ({ElementType.ANNOTATION_TYPE,ElementType.FIELD,ElementType.METHOD})
public @interface EnumValid {
String message() default "值不匹配";
Class>[] groups() default {};
Class extends Payload>[] payload() default {};
Class extends Enum extends AbsValid>> enumClass();
}
校验器-改进后
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class EnumValidator implements ConstraintValidator自定义枚举-校验使用{ private Class extends Enum extends AbsValid>> enumClass; private Enum extends AbsValid>[] ecs; @Override public void initialize(EnumValid ca) { enumClass = ca.enumClass(); // 获取枚举列表 ecs = enumClass.getEnumConstants(); } @Override public boolean isValid(String val, ConstraintValidatorContext cvc) { if( null == val || val.isEmpty() ){ return true; } if( null == enumClass || null == ecs){ return true; } for (Enum extends AbsValid> ec : ecs) { if(((AbsValid) ec).eq(val)){ return true; } } return false; } }
public enum GenderEnum implements AbsValid {
M("1","男"),
W("2","女"),
X("9","未知"),
;
private final String key;
private final String name;
GenderEnum(String key,String name){
this.key = key;
this.name = name;
}
@Override
public boolean eq(String val) {
return key.equals(val);
}
@Override
public String key() {
return key;
}
public String getName() {
return name;
}
}
测试-bean
import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Validated
public class ValidBean {
@NotNull (message = "name不能为null")
private Long id;
@NotBlank (message = "name不能为空")
private String name;
@NotBlank(message = "gender不能为空")
@EnumValid(enumClass = GenderEnum.class, message = "gender非法")
private String gender;
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public ValidBean id(Long id) { this.id = id; return this; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public ValidBean name(String name) { this.name = name;return this; }
public String getGender() { return gender; }
public void setGender(String gender) { this.gender = gender; }
public ValidBean gender(String gender) { this.gender = gender;return this; }
@Override
public String toString() {
return "ValidBean{id=" + id + ", name='" + name + "', gender='" + gender + "'}";
}
}
测试类
import org.hibernate.validator.HibernateValidator;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.Set;
public class ValidTest {
public static void main(String[] args) {
Validator validator = Validation.byProvider(HibernateValidator.class)
.configure().failFast(true).buildValidatorFactory().getValidator();
ValidBean bean = new ValidBean().id(1L).name("a").gender("1");
valid(validator,bean);
bean = new ValidBean().id(1L).name("b").gender("3");
valid(validator,bean);
bean = new ValidBean().id(null).name(null).gender(null);
valid(validator,bean);
}
public static void valid(Validator validator,T t){
System.out.println("start:"+t);
Set> vs = validator.validate(t);
if( null == vs || vs.isEmpty()){
System.out.println("通过");
}else {
for (ConstraintViolation v : vs) {
System.out.println("异常:"+v.getMessage());
}
}
System.out.println("end<<<<");
}
}
测试-日志
start:ValidBean{id=1, name='a', gender='1'}
通过
end<<<<
start:ValidBean{id=1, name='b', gender='3'}
异常:gender非法
end<<<<
start:ValidBean{id=null, name='null', gender='null'}
异常:gender不能为空
end<<<<
使用-webflux
webflux容器启动模式下,hibernate-validator使用会报错,除了pom其他都相同
pom
spring-boot-starter-parent org.springframework.boot 2.3.1.RELEASE org.springframework.boot spring-boot-starter-webflux org.hibernate.validator hibernate-validator
执行ValidTest测试类,报错
16:06:25.298 [main] DEBUG org.jboss.logging - Logging Provider: org.jboss.logging.Log4j2LoggerProvider 16:06:25.309 [main] INFO org.hibernate.validator.internal.util.Version - HV000001: Hibernate Validator 6.1.5.Final 16:06:25.323 [main] DEBUG org.hibernate.validator.internal.xml.config.ValidationXmlParser - Trying to load META-INF/validation.xml for XML based Validator configuration. 16:06:25.326 [main] DEBUG org.hibernate.validator.internal.xml.config.ResourceLoaderHelper - Trying to load META-INF/validation.xml via TCCL 16:06:25.327 [main] DEBUG org.hibernate.validator.internal.xml.config.ResourceLoaderHelper - Trying to load META-INF/validation.xml via Hibernate Validator's class loader 16:06:25.328 [main] DEBUG org.hibernate.validator.internal.xml.config.ValidationXmlParser - No META-INF/validation.xml found. Using annotation based configuration only. 16:06:25.341 [main] DEBUG org.hibernate.validator.internal.engine.resolver.TraversableResolvers - Cannot find javax.persistence.Persistence on classpath. Assuming non JPA 2 environment. All properties will per default be traversable. 16:06:25.405 [main] DEBUG org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator - Failed to load expression factory via classloader sun.misc.Launcher$AppClassLoader@18b4aac2 java.lang.NoClassDefFoundError: javax/el/ExpressionFactory at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.canLoadExpressionFactory(ResourceBundleMessageInterpolator.java:216) at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.buildExpressionFactory(ResourceBundleMessageInterpolator.java:170) at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.解决方法(ResourceBundleMessageInterpolator.java:94) at org.hibernate.validator.internal.engine.AbstractConfigurationImpl.getDefaultMessageInterpolator(AbstractConfigurationImpl.java:570) at org.hibernate.validator.internal.engine.AbstractConfigurationImpl.getDefaultMessageInterpolatorConfiguredWithClassLoader(AbstractConfigurationImpl.java:790) at org.hibernate.validator.internal.engine.AbstractConfigurationImpl.getMessageInterpolator(AbstractConfigurationImpl.java:480) at org.hibernate.validator.internal.engine.ValidatorFactoryImpl. (ValidatorFactoryImpl.java:151) at org.hibernate.validator.HibernateValidator.buildValidatorFactory(HibernateValidator.java:38) at org.hibernate.validator.internal.engine.AbstractConfigurationImpl.buildValidatorFactory(AbstractConfigurationImpl.java:430) at *.*.*.*.test.ValidTest.main(ValidTest.java:20) Caused by: java.lang.ClassNotFoundException: javax.el.ExpressionFactory at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 10 common frames omitted 16:06:25.407 [main] DEBUG org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator - Failed to load expression factory via classloader sun.misc.Launcher$AppClassLoader@18b4aac2 java.lang.NoClassDefFoundError: javax/el/ExpressionFactory at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.canLoadExpressionFactory(ResourceBundleMessageInterpolator.java:216) at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.buildExpressionFactory(ResourceBundleMessageInterpolator.java:183) at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator. (ResourceBundleMessageInterpolator.java:94) at org.hibernate.validator.internal.engine.AbstractConfigurationImpl.getDefaultMessageInterpolator(AbstractConfigurationImpl.java:570) at org.hibernate.validator.internal.engine.AbstractConfigurationImpl.getDefaultMessageInterpolatorConfiguredWithClassLoader(AbstractConfigurationImpl.java:790) at org.hibernate.validator.internal.engine.AbstractConfigurationImpl.getMessageInterpolator(AbstractConfigurationImpl.java:480) at org.hibernate.validator.internal.engine.ValidatorFactoryImpl. (ValidatorFactoryImpl.java:151) at org.hibernate.validator.HibernateValidator.buildValidatorFactory(HibernateValidator.java:38) at org.hibernate.validator.internal.engine.AbstractConfigurationImpl.buildValidatorFactory(AbstractConfigurationImpl.java:430) at *.*.*.*.test.ValidTest.main(ValidTest.java:20) Exception in thread "main" javax.validation.ValidationException: HV000183: Unable to initialize 'javax.el.ExpressionFactory'. Check that you have the EL dependencies on the classpath, or use ParameterMessageInterpolator instead at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.buildExpressionFactory(ResourceBundleMessageInterpolator.java:199) at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator. (ResourceBundleMessageInterpolator.java:94) at org.hibernate.validator.internal.engine.AbstractConfigurationImpl.getDefaultMessageInterpolator(AbstractConfigurationImpl.java:570) at org.hibernate.validator.internal.engine.AbstractConfigurationImpl.getDefaultMessageInterpolatorConfiguredWithClassLoader(AbstractConfigurationImpl.java:790) at org.hibernate.validator.internal.engine.AbstractConfigurationImpl.getMessageInterpolator(AbstractConfigurationImpl.java:480) at org.hibernate.validator.internal.engine.ValidatorFactoryImpl. (ValidatorFactoryImpl.java:151) at org.hibernate.validator.HibernateValidator.buildValidatorFactory(HibernateValidator.java:38) at org.hibernate.validator.internal.engine.AbstractConfigurationImpl.buildValidatorFactory(AbstractConfigurationImpl.java:430) at *.*.*.*.test.ValidTest.main(ValidTest.java:20) Caused by: java.lang.NoClassDefFoundError: javax/el/ELManager at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.buildExpressionFactory(ResourceBundleMessageInterpolator.java:191) ... 8 more Caused by: java.lang.ClassNotFoundException: javax.el.ELManager at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 9 more
将 hibernate-validator 改为 spring-boot-starter-validation,其中有jakarta.el替代实现el
org.springframework.boot spring-boot-starter-validation
日志
start:ValidBean{id=1, name='a', gender='1'}
通过
end<<<<
start:ValidBean{id=1, name='b', gender='3'}
异常:gender非法
end<<<<
start:ValidBean{id=null, name='null', gender='null'}
异常:name不能为空
end<<<<
校验注解列表
bean-javax提供
| 注解 | 说明 |
|---|---|
| @AssertTrue | 用于boolean字段,该字段只能为true |
| @AssertFalse | 该字段的值只能为false |
| @DecimalMax | 只能小于或等于该值 |
| @DecimalMin | 只能大于或等于该值 |
| @Digits(integer=,fraction=) | 检查是否是一种数字的整数、分数,小数位数的数字 |
| @Future | 检查该字段的日期是否是属于将来的日期 |
| @Max | 该字段的值只能小于或等于该值 |
| @Min | 该字段的值只能大于或等于该值 |
| @NotNull | 不能为null,任何对象的value不能为null |
| @Null | 检查该字段为空 |
| @Past | 检查该字段的日期是在过去 |
| @Pattern(regex=,flag=) | 被注释的元素必须符合指定的正则表达式 |
| @Size(min=, max=) | 检查该字段的size是否在min和max之间,可以是字符串、数组、集合、Map等 |
| @Valid | 该注解主要用于字段为一个包含其他对象的集合或map或数组的字段,或该字段直接为一个其他对象的引用,这样在检查当前对象的同时也会检查该字段所引用的对象 |
| 注解 | 说明 |
|---|---|
| 检查是否是一个有效的email地址 | |
| @Length(min=,max=) | 检查所属的字段的长度是否在min和max之间,只能用于字符串 |
| @NotEmpty | 不能为空,这里的空是指空字符串,集合对象的元素不为0,即集合不为空,也可以用于字符串不为null |
| @Range(min=,max=,message=) | 被注释的元素必须在合适的范围内 |
| @NotBlank | 不能为空,检查时会将空格忽略,只能用于字符串不为null,并且字符串trim()以后length要大于0 |
| @URL(protocol=,host=, port=, regexp=, flags=) | 检查是否是一个有效的URL,如果提供了protocol,host等,则该URL还需满足提供的条件 |
| @CreditCardNumber | 对信用卡号进行一个大致的验证 |
| @Validated | 支持分组,用在类型、方法和方法参数上。但不能用在成员属性上 |



