We often encounter parameter verification problems in business, such as front-end parameter verification and Kafka message parameter verification, etc. If the business logic is complex and there are many entities, we verify these data one by one through the code, and there will be a lot of repeated codes and logic irrelevant to the main business. Spring MVC provides a validation mechanism for parameters, but the underlying validation is done through Hibernate, so it’s worth taking a look at Hibernate validation and the JSR validation specification.

JSR data verification specification

Java has officially released JSR303 and JSR349 to propose a standard framework for data validity verification: BeanValidator. In the BeanValidator framework, users specify validation rules by annotating the attributes of beans with standard annotations like @notnull and @max, and verify beans through standard validation interfaces.

List of JSR annotations

Data validation annotations in the JSR standard are as follows:

Annotation name Annotation data type Annotations role The sample
AssertFalse boolean/Boolean The annotated element must be False @AssertFalse private boolean success;
AssertTrue boolean/Boolean The annotated element must be True @AssertTrue private boolean success;
DecimalMax BigDecimal/BigInteger/CharSequence/byte/short/int/long and wrapper classes The annotated value should be less than or equal to the specified maximum value @DecimalMax("10") private BigDecimal value;
DecimalMin BigDecimal/BigInteger/CharSequence/byte/short/int/long and wrapper classes The annotated value should be greater than or equal to the specified minimum @DecimalMin("10") private BigDecimal value;
Digits BigDecimal/BigInteger/CharSequence/byte/short/int/long and wrapper classes Integer specifies the maximum number of digits of the integer part, and fraction specifies the maximum number of digits of the decimal part @Digits(integer = 10,fraction = 4) private BigDecimal value;
Email CharSequence The string is a valid email address format @Email private String email;
Future Various date types in Java The specified date should be after the current date @Future private LocalDateTime future;
FutureOrPresent Various date types in Java The specified date should be the current date or after the current date @FutureOrPresent private LocalDateTime futureOrPresent;
Max BigDecimal/BigInteger/byte/short/int/long and wrapper classes The annotated value should be less than or equal to the specified maximum value @Max("10") private BigDecimal value;
Min BigDecimal/BigInteger/byte/short/int/long and wrapper classes The annotated value should be greater than or equal to the specified minimum @Min("10") private BigDecimal value;
Negative BigDecimal BigInteger/byte/short/int/long/float/double and wrapper classes The commented value should be negative @Negative private BigDecimal value;
NegativeOrZero BigDecimal BigInteger/byte/short/int/long/float/double and wrapper classes The commented value should be 0 or negative @NegativeOrZero private BigDecimal value;
NotBlank CharSequence The annotated string contains at least one non-null character @NotBlank private String noBlankString;
NotEmpty CharSequence/Collection/Map/Array The number of annotated collection elements is greater than 0 @NotEmpty private List<string> values;
NotNull any Annotated values are not empty @NotEmpty private Object value;
Null any The annotated value must be null @Null private Object value;
Past Various date types in Java The specified date should be before the current date @Past private LocalDateTime past;
PastOrPresent Various date types in Java The specified date should be on or before the current date @PastOrPresent private LocalDateTime pastOrPresent;
Pattern CharSequence The annotated string should conform to the given regular expression @Pattern(\d*) private String numbers;
Positive BigDecimal BigInteger/byte/short/int/long/float/double and wrapper classes The commented value should be positive @Positive private BigDecimal value;
PositiveOrZero BigDecimal BigInteger/byte/short/int/long/float/double and wrapper classes The commented value should be positive or 0 @PositiveOrZero private BigDecimal value;
Size CharSequence/Collection/Map/Array The number of annotated collection elements is within the specified range @Size(min=1,max=10) private List<string> values;

JSR annotation content

Take the simple @notnull annotation as an example to see what it contains. As shown in the source code below, the @notnull annotation contains the following:

  1. Message: Error message, in this case error code, which can be translated into different languages depending on internationalization.
  2. Groups: Groups check. Different groups can have different check conditions. For example, the same DTO used for create and Update may have different check conditions.
  3. Payload: The user of the BeanValidation API can use this attribute to specify the severity level of a constraint. This property is not used by the API itself.
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
@Documented
@Constraint(validatedBy = { })
public @interface NotNull {
	String message(a) default "{javax.validation.constraints.NotNull.message}"; Class
      [] groups() default { }; Class
      [] payload() default { }; /** * Defines several {@link NotNull} annotations on the same element. */ @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Retention(RUNTIME) @Documented @interface List { NotNull[] value(); }}Copy the code

We use error messages such as “Message” and “group” in many applications. In my article about the Spring Validator data verification, I explain the details about the payload. In the following example, we illustrate the usage of the following payload. Payload is used to indicate the severity of a data verification failure. After verifying an example of ContactDetails, . You can by calling ConstraintViolation getConstraintDescriptor () getPayload before () to get assigned to the level of error, and can according to this information to decide the next to the behavior.

public class Severity {
    public static class Info extends Payload {};
    public static class Error extends Payload {};
}

public class ContactDetails {
    @NotNull(message="Name is mandatory", payload=Severity.Error.class)
    private String name;

    @NotNull(message="Phone number not specified, but not mandatory", payload=Severity.Info.class)
    private String phoneNumber;

    // ...
}
Copy the code

JSR verification interface

Using the JSR validation annotations above, we can add validation conditions to the corresponding fields of a class. How do we validate these validation conditions?

Set

> validate(T object, Class
… groups); , this method can be used to verify whether an Object complies with the verification rules of a specified group. If no group is specified, only the verification rules of the default group take effect.

public interface Validator {

	/**
	 * Validates all constraints on {@code object}.
	 */<T> Set<ConstraintViolation<T>> validate(T object, Class<? >... groups);/**
	 * Validates all constraints placed on the property of {@code object}
	 * named {@code propertyName}.
	 */<T> Set<ConstraintViolation<T>> validateProperty(T object, String propertyName,Class<? >... groups);/**
	 * Validates all constraints placed on the property named {@code propertyName}
	 * of the class {@code beanType} would the property value be {@code value}.
	 */<T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType, String propertyName, Object value, Class<? >... groups);/**
	 * Returns the descriptor object describing bean constraints.
	 * The returned object (and associated objects including
	 * {@link ConstraintDescriptor}s) are immutable.
	 */
	BeanDescriptor getConstraintsForClass(Class
        clazz);

	/** * Returns an instance of the specified type allowing access to * provider-specific APIs. * <p> * If the Jakarta Bean  Validation provider implementation does not support * the specified class, {@link ValidationException} is thrown.call
	 */
	<T> T unwrap(Class<T> type);

	/** * Returns the contract for validating parameters and return values of methods * and constructors. */
	ExecutableValidator forExecutables(a);
}
Copy the code

Hibernate data verification

Based on the JSR data validation specification, Hibernate adds some new annotation validation and then implements JSR’s Validator interface for data validation.

Hibernate new Annotations

Annotation name Annotation data type Annotations role The sample
CNPJ CharSequence The annotated element must be the national registration number of a legal Brazilian corporation @CNPJ private String cnpj;
CPF CharSequence The annotated element must be a valid Brazilian taxpayer registration number @CPF private String cpf;
TituloEleitoral CharSequence The annotated element must be a valid Brazilian voter ID number @TituloEleitoral private String tituloEleitoral;
NIP CharSequence The annotated element must be a valid Polish tax code @NIP private String nip;
PESEL CharSequence The annotated element must be a valid Polish id number @PESEL private String pesel;
REGON CharSequence Annotated elements must be numbered with a valid Polish region @REGON private String regon;
DurationMax Duration The annotated element Duration has a length less than the specified length @DurationMax(day=1) private Duration duration;
DurationMin Duration The annotated element Duration is longer than the specified Duration @DurationMin(day=1) private Duration duration;
CodePointLength CharSequence CodePoint Number of annotated elements Within a specified range, each character in Unicode has a unique identifier, called a CodePoint. For example, if we want to limit the number of Chinese characters, we can use this @CodePointLength(min=1) private String name;
ConstraintComposition Other data validation annotations The combinatorial relationship of a combinatorial annotation, and or
CreditCardNumber CharSequence Used to determine if a credit card is in a valid format @CreditCardNumber private String credictCardNumber;
Currency CharSequence The annotated element is the exchange rate of the specified type @Currency(value = {"USD"}) private String currency;
ISBN CharSequence The annotated element is a valid ISBN number @ISBN private String isbn;
Length CharSequence Annotated elements are within a specified length @Length(min=1) private String name;
LuhnCheck CharSequence Annotated elements can be checked by the Luhn algorithm @LuhnCheck private String luhn;
Mod10Check CharSequence Annotated elements can be checked by the modulo 10 algorithm @Mod10Check private String mod10;
ParameterScriptAssert methods Parameter script verification — — —
ScriptAssert class Class script verification — — —
UniqueElements A collection of Each element in the set is unique @UniqueElements private List<String> elements;

Hibiernate data verification

How to use Hibernate for data validation? Hibernate implements the Validator interface in the ValidatorImpl class. We can provide the factory class by Hibernate HibernateValidator. Create a ValidatorImpl buildValidatorFactory examples. The code to create a Validator instance using Hibernate is shown below.

ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
    .configure()
    .addProperty( "hibernate.validator.fail_fast"."true" )
    .buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
Copy the code

Hibernate verification source code

Hibernate uses factory methods to instantiate an instance of the Validator interface that can be used in a validation JavaBean with validation annotations. How does Hibernate implement this validation logic underneath? We take the following JavaBean as an example to parse the source code of Hibernate verification.

@Data
public class Person {

    @NotBlank
    @Size(max=64)
    private String name;

    @Min(0)
    @Max(200)
    private int age;
}
Copy the code

ConstraintValidator introduction

ConstraintValidator is the most fine-grained validation of data in Hibernate. It verifies that values of specified annotations and types are valid. @max (200)private int age; The age field is validated with a ConstraintValidator called MaxValidatorForInteger, which checks whether the specified value is greater than the specified maximum value.

public class MaxValidatorForInteger extends AbstractMaxValidator<Integer> {

	@Override
	protected int compare(Integer number) {
		returnNumberComparatorHelper.compare( number.longValue(), maxValue ); }}public abstract class AbstractMaxValidator<T> implements ConstraintValidator<Max.T> {

	protected long maxValue;

	@Override
	public void initialize(Max maxValue) {
		this.maxValue = maxValue.value();
	}

	@Override
	public boolean isValid(T value, ConstraintValidatorContext constraintValidatorContext) {
		// null values are valid
		if ( value == null ) {
			return true;
		}

		return compare( value ) <= 0;
	}

	protected abstract int compare(T number);
}
Copy the code

ConstraintValidator initialization

Hibernate provides a ValidatorImpl for verifying data. What is the relationship between ValidatorImpl and ConstraintValidator? Simply put, the ValidatorImpl initializes all ConstraintValidators and calls the built-in ConstraintValidator validation data during validation. The @constraint (validatedBy = {}) annotation for the built-in ConstraintValidator is empty.

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(List.class)
@Documented
@Constraint(validatedBy = { }) // This is empty
public @interface AssertFalse {

	String message(a) default "{javax.validation.constraints.AssertFalse.message}"; Class
      [] groups() default { }; Class
      [] payload() default { }; /** * Defines several {@link AssertFalse} annotations on the same element. * * @see javax.validation.constraints.AssertFalse */ @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Retention(RUNTIME) @Documented @interface List { AssertFalse[] value(); }}Copy the code

Custom ConstraintValidator

If the annotations in Hibernate and JSR are not enough for me and I need to define a custom annotation and constraint, how should we implement it? Implementing a custom validation logic consists of two steps: 1. Annotation implementation. 2. Verify the implementation of logic. For example, if we need an annotation to verify the state of a field, we can define an annotation using the following example:

@Target( { METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = StatusValidator.class)
@Documented
public @interface ValidStatus {
    String message(a) default"Status error"; Class<? >[] groups()default {};
    Class<? extends Payload>[] payload() default {};
    /** * a valid set of status values, default {1,2} */
    int[] value() default {1.2};
}
Copy the code

Once we have implemented the annotation, we need to implement @constraint (validatedBy = StatusValidator.class) in the annotation with the following example code:

/** * Validates whether the state belongs to the specified state set (ConstraintValidator 
      
       ) */
      ,>
public class StatusValidator implements ConstraintValidator<ValidStatus.Integer> {
    private Integer[] validStatus;

    @Override
    public void initialize(ValidStatus validStatus) {
        int[] ints = validStatus.value();
        int n = ints.length;
        Integer[] integers = new Integer[n];
        for (int i = 0; i < n; i++) {
            integers[i] = ints[i];
        }
        this.validStatus = integers;
    }

    @Override
    public boolean isValid(Integer n, ConstraintValidatorContext constraintValidatorContext) {
        List<Integer> status = Arrays.asList(validStatus);
        if (status.contains(n)) {
            return true;
        }
        return false; }}Copy the code

The characteristics of the Validator

Four levels of constraint

Constraints at the member variable level

Constraints can be expressed by annotating a class’s member variables. The following code looks like this:

@Data
public class Person {

    @NotBlank
    @Size(max=64)
    private String name;

    @Min(0)
    @Max(200)
    private int age;
}
Copy the code

Attribute constraints

If your model class follows javabean standards, it may also annotate the bean’s properties instead of its member variables. An introduction to Javabeans can be found in my other blog post.

@Data
public class Person {

    private String name;

    @Min(0)
    @Max(200)
    private int age;

    @NotBlank
    @Size(max=64)
    public String getName(a){
        returnname; }}Copy the code

A collection of constraints

Constraints on elements in the container can be implemented by specifying elementType.type_use in the constraint definition in the @target annotation of the constraint annotation

Class level constraints

A constraint is placed at the class level; in this case, the object being validated is not simply a property, but a complete object. Using class-level constraints, you can verify correlations between several attributes of an object, such as not allowing all fields to be null at the same time.

@Data
@NotAllFieldNull
public class Person {

    private String name;

    @Min(0)
    @Max(200)
    private int age;

    @NotBlank
    @Size(max=64)
    public String getName(a){
        returnname; }}Copy the code

Verify the inheritability of annotations

The parent class adds a field to the constraint, and the child class verifies the field in the parent class.

Recursive check

Suppose we have a field of type Address in our example above, and Address has its own validation. How do we verify the field in Address? You can implement recursive validation by adding an @valid annotation to Address.

@Data
public class Person {

    private String name;

    @Min(0)
    @Max(200)
    private int age;

    @Valid
    public Address address;
}

@Data
public class Address{

    @NotNull
    private string city;
}
Copy the code

Method parameter verification

We can implement method-level parameter validation by adding validation annotations to method parameters, although these annotations need to be implemented using some AOP implementation (such as Spring’s method parameter validation).


public void createPerson(@NotNull String name,@NotNull Integer age){}Copy the code

Method parameter cross verification

Methods also support validation between parameters. For example, the following annotation does not allow the user name and age to be null when creating a user. The parameter of cross-check is Object[], and different parameter positions correspond to different Obj.

@NotAllPersonFieldNull
public void createPerson( String name,Integer age){}Copy the code

Method return value validation

public @NotNull Person getPerson( String name,Integer age){
    return null;
}

Copy the code

Group function

As I mentioned in another article on Spring validation annotations, the @VALID annotation does not support group validation in Spring’s validation system. The @validated annotation supports group validation. It’s not that the @VALID annotation in JSR doesn’t support grouping validation. It’s that the Spring layer has disabled the grouping validation for the @VALID annotation.

So both native JSR annotations and Hibernate validations support grouping validation. See my article on Spring validation for the validation logic.

Grouping inheritance

The JSR group verification function uses the group field in the annotation. The group field stores the category of the group. If there is an inheritance relationship between the groups, will the group verification be inherited? The answer is yes.

Group order

If we need to specify the check order during the check, we can group the check conditions, and then the properties of the object are checked in order.

GroupSequence({ Default.class, BaseCheck.class, AdvanceCheck.class }) public interface OrderedChecks { }

Payload

So if you want to have different validation methods in different situations, like in English or something like that, you can’t use groups, so you can use PayLoad. The user can specify the payload of the current environment when initializing the Validator. In the verification process, the user can obtain the payload of the environment and perform different verification processes:

ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
        .configure()
        .constraintValidatorPayload( "US" )
        .buildValidatorFactory();

Validator validator = validatorFactory.getValidator();

public class ZipCodeValidator implements ConstraintValidator<ZipCode.String> {

    public String countryCode;

    @Override
    public boolean isValid(String object, ConstraintValidatorContext constraintContext) {
        if ( object == null ) {
            return true;
        }

        boolean isValid = false;

        String countryCode = constraintContext
                .unwrap( HibernateConstraintValidatorContext.class )
                .getConstraintValidatorPayload( String.class );

        if ( "US".equals( countryCode ) ) {
            // checks specific to the United States
        }
        else if ( "FR".equals( countryCode ) ) {
            // checks specific to France
        }
        else {
            // ...
        }

        returnisValid; }}Copy the code

I am the god of the royal Fox. Welcome to follow my wechat official account: Wzm2ZSD

This article was first published to wechat public account, all rights reserved, reprint prohibited!