spring-java使用Validation参数验证-自定义校验器

29 篇文章 0 订阅
23 篇文章 0 订阅

在开发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框架的自定义注解实现必要容易

  1. 自定义一个注解类
  2. 重写校验器

定义接口-固定校验方法

参考别人的自定义代码时候,发现使用反射方式+方法字符串,执行验证方法。就考虑怎么固定方法并且不适用反射方式。通过接口方式就实现了。

/**
 * 枚举接口,主要是固定方法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检查是否是一个有效的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支持分组,用在类型、方法和方法参数上。但不能用在成员属性上
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值