java bean验证 Jakarta Validation与hibernate-validator以及spring-boot-starter-validation区别 自定义验证注解

目录


关于 bean 验证的规范官网

https://beanvalidation.org/

看官网目前只有 java 语言的,其他语言的还没有。

一般一个规范不局限于一种具体的语言。例如算法和设计模式,一个思想实现可以通过多种方式来实现。语言只是一种实现思想的工具。

在java中对应的规范是eclipse下的Jakarta Validation

https://github.com/jakartaee/validation

类似于 jdbc 规范,java 只是规定了连接数据库和crud的操作,但是具体各个数据库怎么来实现这些操作是各家数据库厂商的问题。也想起了电脑上的usb插口,什么设备都可以插还能识别正常运行。

maven 依赖

<dependency>
	<groupId>jakarta.validation</groupId>
	<artifactId>jakarta.validation-api</artifactId>
</dependency>

针对此规范开源社区中的 hibernate 进行了规范的实现

官网

https://hibernate.org/validator/

源码

https://github.com/hibernate/hibernate-validator

maven依赖

<dependency>
  <groupId>org.hibernate.validator</groupId>
  <artifactId>hibernate-validator</artifactId>
  <version>6.2.5.Final</version>
  <scope>compile</scope>
</dependency>

 通过 ResourceBundle 进行了国际化处理 

通过内容可以看出,hibernate-validator 针对 规范做了扩充

javax.validation.constraints.AssertFalse.message     = must be false
javax.validation.constraints.AssertTrue.message      = must be true
javax.validation.constraints.DecimalMax.message      = must be less than ${inclusive == true ? 'or equal to ' : ''}{value}
javax.validation.constraints.DecimalMin.message      = must be greater than ${inclusive == true ? 'or equal to ' : ''}{value}
javax.validation.constraints.Digits.message          = numeric value out of bounds (<{integer} digits>.<{fraction} digits> expected)
javax.validation.constraints.Email.message           = must be a well-formed email address
javax.validation.constraints.Future.message          = must be a future date
javax.validation.constraints.FutureOrPresent.message = must be a date in the present or in the future
javax.validation.constraints.Max.message             = must be less than or equal to {value}
javax.validation.constraints.Min.message             = must be greater than or equal to {value}
javax.validation.constraints.Negative.message        = must be less than 0
javax.validation.constraints.NegativeOrZero.message  = must be less than or equal to 0
javax.validation.constraints.NotBlank.message        = must not be blank
javax.validation.constraints.NotEmpty.message        = must not be empty
javax.validation.constraints.NotNull.message         = must not be null
javax.validation.constraints.Null.message            = must be null
javax.validation.constraints.Past.message            = must be a past date
javax.validation.constraints.PastOrPresent.message   = must be a date in the past or in the present
javax.validation.constraints.Pattern.message         = must match "{regexp}"
javax.validation.constraints.Positive.message        = must be greater than 0
javax.validation.constraints.PositiveOrZero.message  = must be greater than or equal to 0
javax.validation.constraints.Size.message            = size must be between {min} and {max}

org.hibernate.validator.constraints.CreditCardNumber.message        = invalid credit card number
org.hibernate.validator.constraints.Currency.message                = invalid currency (must be one of {value})
org.hibernate.validator.constraints.EAN.message                     = invalid {type} barcode
org.hibernate.validator.constraints.Email.message                   = not a well-formed email address
org.hibernate.validator.constraints.ISBN.message                    = invalid ISBN
org.hibernate.validator.constraints.Length.message                  = length must be between {min} and {max}
org.hibernate.validator.constraints.CodePointLength.message         = length must be between {min} and {max}
org.hibernate.validator.constraints.LuhnCheck.message               = the check digit for ${validatedValue} is invalid, Luhn Modulo 10 checksum failed
org.hibernate.validator.constraints.Mod10Check.message              = the check digit for ${validatedValue} is invalid, Modulo 10 checksum failed
org.hibernate.validator.constraints.Mod11Check.message              = the check digit for ${validatedValue} is invalid, Modulo 11 checksum failed
org.hibernate.validator.constraints.ModCheck.message                = the check digit for ${validatedValue} is invalid, {modType} checksum failed
org.hibernate.validator.constraints.Normalized.message              = must be normalized
org.hibernate.validator.constraints.NotBlank.message                = may not be empty
org.hibernate.validator.constraints.NotEmpty.message                = may not be empty
org.hibernate.validator.constraints.ParametersScriptAssert.message  = script expression "{script}" didn't evaluate to true
org.hibernate.validator.constraints.Range.message                   = must be between {min} and {max}
org.hibernate.validator.constraints.ScriptAssert.message            = script expression "{script}" didn't evaluate to true
org.hibernate.validator.constraints.UniqueElements.message          = must only contain unique elements
org.hibernate.validator.constraints.URL.message                     = must be a valid URL

org.hibernate.validator.constraints.br.CNPJ.message                 = invalid Brazilian corporate taxpayer registry number (CNPJ)
org.hibernate.validator.constraints.br.CPF.message                  = invalid Brazilian individual taxpayer registry number (CPF)
org.hibernate.validator.constraints.br.TituloEleitoral.message      = invalid Brazilian Voter ID card number

org.hibernate.validator.constraints.pl.REGON.message                = invalid Polish Taxpayer Identification Number (REGON)
org.hibernate.validator.constraints.pl.NIP.message                  = invalid VAT Identification Number (NIP)
org.hibernate.validator.constraints.pl.PESEL.message                = invalid Polish National Identification Number (PESEL)

org.hibernate.validator.constraints.ru.INN.message                  = invalid Russian taxpayer identification number (INN)

org.hibernate.validator.constraints.time.DurationMax.message        = must be shorter than${inclusive == true ? ' or equal to' : ''}${days == 0 ? '' : days == 1 ? ' 1 day' : ' ' += days += ' days'}${hours == 0 ? '' : hours == 1 ? ' 1 hour' : ' ' += hours += ' hours'}${minutes == 0 ? '' : minutes == 1 ? ' 1 minute' : ' ' += minutes += ' minutes'}${seconds == 0 ? '' : seconds == 1 ? ' 1 second' : ' ' += seconds += ' seconds'}${millis == 0 ? '' : millis == 1 ? ' 1 milli' : ' ' += millis += ' millis'}${nanos == 0 ? '' : nanos == 1 ? ' 1 nano' : ' ' += nanos += ' nanos'}
org.hibernate.validator.constraints.time.DurationMin.message        = must be longer than${inclusive == true ? ' or equal to' : ''}${days == 0 ? '' : days == 1 ? ' 1 day' : ' ' += days += ' days'}${hours == 0 ? '' : hours == 1 ? ' 1 hour' : ' ' += hours += ' hours'}${minutes == 0 ? '' : minutes == 1 ? ' 1 minute' : ' ' += minutes += ' minutes'}${seconds == 0 ? '' : seconds == 1 ? ' 1 second' : ' ' += seconds += ' seconds'}${millis == 0 ? '' : millis == 1 ? ' 1 milli' : ' ' += millis += ' millis'}${nanos == 0 ? '' : nanos == 1 ? ' 1 nano' : ' ' += nanos += ' nanos'}

spring boot

在 spring boot 的 starter中有一个专门是关于验证的,正是用的 hibernate-validator。

这里有一个疑问:我所知的开源的关于验证规范的实现只有 hibernate 做了,所以用了这个?

在项目中要使用自动验证需要先做好配置

public Validator validator() {
        return Validation.byProvider(HibernateValidator.class).configure().failFast(true).buildValidatorFactory().getValidator();
    }

    @Bean
    public MethodValidationPostProcessor methodValidationPostProcessor() {
        MethodValidationPostProcessor processor = new MethodValidationPostProcessor();
        processor.setValidator(validator());
        return processor;
    }

然后在java代码中引入 javax.validation 下的相关注解

public class AdminLoginParam {
    @NotEmpty
    @ApiModelProperty(value = "用户名",required = true)
    private String username;
    @NotEmpty
    @ApiModelProperty(value = "密码",required = true)
    private String password;
}

在 controller 的方法中添加 @Validated 或者 @Valid 注解开启验证 

public CommonResult<Admin> register(@Validated @RequestBody AdminParam adminParam) {
        Admin admin = adminService.register(adminParam);
        if (admin == null) {
            return CommonResult.failed();
        }
        return CommonResult.success(admin);
    }

关于验证的注解 

javax.validation.Valid
org.springframework.validation.annotation.Validated
在 spring 的工具类 org.springframework.validation.annotation.ValidationAnnotationUtils
public abstract class ValidationAnnotationUtils {

	private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];

	/**
	 * Determine any validation hints by the given annotation.
	 * <p>This implementation checks for {@code @javax.validation.Valid},
	 * Spring's {@link org.springframework.validation.annotation.Validated},
	 * and custom annotations whose name starts with "Valid".
	 * @param ann the annotation (potentially a validation annotation)
	 * @return the validation hints to apply (possibly an empty array),
	 * or {@code null} if this annotation does not trigger any validation
	 */
	@Nullable
	public static Object[] determineValidationHints(Annotation ann) {
		Class<? extends Annotation> annotationType = ann.annotationType();
		String annotationName = annotationType.getName();
		if ("javax.validation.Valid".equals(annotationName)) {
			return EMPTY_OBJECT_ARRAY;
		}
		Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
		if (validatedAnn != null) {
			Object hints = validatedAnn.value();
			return convertValidationHints(hints);
		}
		if (annotationType.getSimpleName().startsWith("Valid")) {
			Object hints = AnnotationUtils.getValue(ann);
			return convertValidationHints(hints);
		}
		return null;
	}

	private static Object[] convertValidationHints(@Nullable Object hints) {
		if (hints == null) {
			return EMPTY_OBJECT_ARRAY;
		}
		return (hints instanceof Object[] ? (Object[]) hints : new Object[]{hints});
	}

}

中 @Validated 或者 @Valid 注解按照同一个处理。

自定义验证注解

编写自定义注解

package com.cmft.common4es.config.validate;

import com.cmft.common4es.config.validate.validator.JsonValueValidator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * json参数格式校验注解
 */
@Target({ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = JsonValueValidator.class)
@Documented
public @interface JsonValue {
    String message() default "{com.cmft.common4es.config.validate.JsonValue.message}";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };
}

自定义注解处理

package com.cmft.common4es.config.validate.validator;

import com.cmft.common4es.config.validate.JsonValue;
import com.cmft.common4es.util.JsonUtils;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.invoke.MethodHandles;

/**
 * json参数格式校验注解验证器
 * @author Rike
 * @date 2023/8/18
 */
public class JsonValueValidator implements ConstraintValidator<JsonValue, Object> {

    private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        // logger.info("{}", value);
        // logger.info("{}", context);
        boolean flag = false;
        try {
            // 校验是否为json数据
            ObjectNode objectNode = (ObjectNode) JsonUtils.getInstance().readTree(value.toString());
            flag = true;
        } catch (Exception e) {
            logger.error("", e);
        }
        return flag;
    }

    @Override
    public void initialize(JsonValue constraintAnnotation) {
        logger.info("{}", MethodHandles.lookup().lookupClass());
    }
}

 使用注解的类

package com.cmft.common4es.entity;

import com.cmft.common4es.config.validate.ExecuteGroup;
import com.cmft.common4es.config.validate.JsonValue;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;

/**
 * 插入数据
 */
@Valid
public class InsertData extends BaseData {

    /**
     * 需要插入的数据
     */
    @NotBlank(groups = {ExecuteGroup.First.class})
    @JsonValue(groups = {ExecuteGroup.Second.class})
    private String data;

    /**
     * 索引刷新策略
     *
     * 0 默认 不指定
     * 1 等待刷新
     * 2 强制刷新
     */
    private String refreshPolicy = "0";

    /**
     * 主键策略
     *
     * 0 自动生成
     * 1 指定
     */
    private String primaryKeyPolicy = "0";

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public String getRefreshPolicy() {
        return refreshPolicy;
    }

    public void setRefreshPolicy(String refreshPolicy) {
        this.refreshPolicy = refreshPolicy;
    }

    public String getPrimaryKeyPolicy() {
        return primaryKeyPolicy;
    }

    public void setPrimaryKeyPolicy(String primaryKeyPolicy) {
        this.primaryKeyPolicy = primaryKeyPolicy;
    }
}

在类上加 @Valid,字段上的注解上指定group

验证内容配置

ValidationMessages_zh_CN.properties

com.cmft.common4es.config.validate.RequestParameterValue.message=
com.cmft.common4es.config.validate.JsonValue.message=非json数据
com.cmft.common4es.config.validate.JsonArrayStringValue.message=非json数组数据

组合校验

验证组

package com.cmft.common4es.config.validate;

import javax.validation.GroupSequence;

@GroupSequence(value = {ExecuteGroup.First.class, ExecuteGroup.Second.class, ExecuteGroup.Third.class})
public @interface ExecuteGroup {

    interface First {}

    interface Second {}

    interface Third {}
}

通过 @GroupSequence 指定验证顺序

在请求方法上使用 @Validated 指定验证组

@PostMapping(value = "insertBatchArrayStr")
public CommonResponse insertBatchArrayStr(@RequestBody @Validated(ExecuteGroup.class) InsertDataJsonArray insertData) {

	String arrayStr = insertData.getJsonArrayStr();
	boolean autoKey = StringUtils.equals(insertData.getPrimaryKeyPolicy(), "0");

	ArrayNode node = (ArrayNode) JsonUtils.readTree(arrayStr);
	List<JsonNode> nodeList = IteratorUtils.toList(node.iterator());
	List<String> list = nodeList.parallelStream().map((JsonNode jsonNode) -> {
		// 指定主键id
		if (!autoKey) {
			JsonNode temp = jsonNode.get("id");
			if (Objects.isNull(temp)) {
				throw new BusinessException("参数中指定了主键策略为自定义,但是参数jsonArrayStr中的数据未指定id");
			}
		}
		return JsonUtils.toJsonString(jsonNode);
	}).collect(Collectors.toList());

	InsertDataBatch insertDataBatch = new InsertDataBatch();
	BeanUtils.copyProperties(insertData, insertDataBatch);
	insertDataBatch.setDataList(list);
	insertService.insertBatch(insertDataBatch);
	return CommonResponse.success();
}

类的属性上添加组合注解,类上添加 @Valid

代码如上

参考链接

https://blog.csdn.net/m0_63164811/article/details/126075344

https://blog.csdn.net/ww2651071028/article/details/130314309

https://blog.csdn.net/xxpxxpoo8/article/details/127551926

https://blog.csdn.net/weixin_43476020/article/details/128984428

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
扎眼的阳光的头像扎眼的阳光普通用户
上一篇 2023年12月21日
下一篇 2023年12月21日

相关推荐