在开发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校验
使用-Tomcat
pom引入jar
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.0.9.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
</dependencies>
配置类,快速失败模式
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);
/** 使用hibernate的注解来进行验证-spring注入 */
@Resource
private Validator validator;
public <T> void validateBean(T t){
logger.debug("使用HibernateValidator校验实体类, {}",t);
Set<ConstraintViolation<T>> validate = validator.validate(t);
if(!validate.isEmpty()){
List<String> list = validate.stream().map(ConstraintViolation::getMessage)
.collect(Collectors.toList());
logger.debug("使用HibernateValidator校验实体类-, {}",list);
throw new RuntimeException(list.get(0));
}
}
/** 使用hibernate的注解来进行验证-静态引入 */
private static Validator validate = Validation.byProvider(HibernateValidator.class).configure()
.failFast(true).buildValidatorFactory().getValidator();
public static <T> void validate(T obj) {
Set<ConstraintViolation<T>> constraintViolations = validate.validate(obj);
// 抛出检验异常
if (constraintViolations.size() > 0) {
throw new RuntimeException(String.format("0001参数校验失败:%s",
constraintViolations.iterator().next().getMessage()));
}
}
}
自定义校验注解
validation框架的自定义注解实现必要容易
- 自定义一个注解类
- 重写校验器
定义接口-固定校验方法
参考别人的自定义代码时候,发现使用反射方式+方法字符串,执行验证方法。就考虑怎么固定方法并且不适用反射方式。通过接口方式就实现了。
/**
* 枚举接口,主要是固定方法eq
*
* @author z.y
* @version v1.0
* @date 2022/7/19
*/
public interface AbsValid {
/**
* 校验 值
* @param val 待校验值
* @return 状态
*/
boolean eq(String val);
/**
* 获取枚举key
* @return 枚举key
*/
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;
/**
* 自定义校验注解-枚举
* @author z.y
* @version v1.0
* @date 2022/7/19
*/
@Constraint (validatedBy = EnumValidator.class)
@Retention (RetentionPolicy.RUNTIME)
@Target ({ElementType.ANNOTATION_TYPE,ElementType.FIELD,ElementType.METHOD})
public @interface EnumValid {
/** 提示信息,必须使用message命名,否则hibernate获取不到提示信息 */
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;
/**
* 自定义校验器
*
* @author z.y
* @version v1.0
* @date 2022/7/19
*/
public class EnumValidator implements ConstraintValidator<EnumValid,String> {
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;
}
}
自定义枚举-校验使用
/**
* 自定义 枚举-性别,实现接口,已支持 hibernate 校验器
*
* @author z.y
* @version v1.0
* @date 2022/7/19
*/
public enum GenderEnum implements AbsValid {
/** 1 男 */M("1","男"),
/** 2 女 */W("2","女"),
/** 9 未知 */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;
/**
* 校验测试实体
*
* @author z.y
* @version v1.0
* @date 2022/7/19
*/
@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;
/**
* 测试校验
*
* @author z.y
* @version v1.0
* @date 2022/7/19
*/
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 <T> void valid(Validator validator,T t){
System.out.println("start:"+t);
Set<ConstraintViolation<T>> vs = validator.validate(t);
if( null == vs || vs.isEmpty()){
System.out.println("通过");
}else {
for (ConstraintViolation<T> 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
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.3.1.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<!--异步非阻塞并不会使程序运行得更快。WebFlux 并不能使接口的响应时间缩短,它仅仅能够提升吞吐量和伸缩性。-->
<!--Spring WebFlux 是一个异步非阻塞的 Web 框架,所以,它特别适合应用在 IO 密集型的服务中,比如微服务网关这样的应用中。-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
</dependencies>
执行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.<init>(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.<init>(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.<init>(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.<init>(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.<init>(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.<init>(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
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
日志
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或数组的字段,或该字段直接为一个其他对象的引用,这样在检查当前对象的同时也会检查该字段所引用的对象 |
Hibernate
注解 | 说明 |
---|---|
检查是否是一个有效的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 | 支持分组,用在类型、方法和方法参数上。但不能用在成员属性上 |