当前位置:网站首页>Bean validation custom container validation chapter ----06

Bean validation custom container validation chapter ----06

2022-07-24 00:37:00 Big flicker love flicker


Preface

This article continues with the above description , Continue Bean Validation Declarative verification of the four levels of : Container element validation ( Custom container type ) And class level validation ( Also called multi field joint validation ).


Custom container type element validation

We already know from the above Bean Validation It can be used for the shape of List、Set、Map The elements in such a container type are verified , The container with built-in support can cover Most of the usage scenarios , But some scenes still can't be covered , And this may be very common .

For example, we are not unfamiliar with the method return value container Result< T >, The structure is like this ( The simplest form , For reference only ):

@Data
public final class Result<T> implements Serializable {
    

    private boolean success = true;
    private T data = null;
    
    private String errCode;
    private String errMsg;
}

Controller It's packed in layers ( load ) data data, It's like this :

@GetMapping("/room")
Result<Room> room() {
     ... }

public class Room {
    
    @NotNull
    public String name;
    @AssertTrue
    public boolean finished;
}

At this time, I hope to be right Result< Room > Inside Room Verify the validity : With the help of BV Do declarative validation instead of hard coding .

I hope it's OK :Result<@Notnull @Valid LoggedAccountResp>. obviously , By default, even if the constraint annotation is declared, it is invalid , After all Bean Validation At all “ incognizance ”Result This “ Containers ”, Not to mention verifying its elements .

Fortunately Bean Validation This provides an extension point . I'm going to provide a step-by-step implementation of this , Let verification elegance rise again .

  • You can customize one from Result< T > Rita took it out T It's worth it ValueExtractor Value extractor

Bean Validation Allow us to support custom container element types .

To support custom container types , Need to register a custom ValueExtractor For value extraction .

public class ResultValueExtractor implements ValueExtractor<Result<@ExtractedValue ?>> {
    
    
    @Override
    public void extractValues(Result<?> originalValue, ValueReceiver receiver) {
    
        receiver.value(null, originalValue.getData());
    }
}
  • Register this custom value extractor with the validator Validator in , And provide test code :

hold Result As a Filed Fields are loaded into Java Bean in :

public class ResultDemo {
    
    public Result<@Valid Room> roomResult;
}

Test code :

public static void main(String[] args) {
    
    Room room = new Room();
    room.name = "DHY";
    Result<Room> result = new Result<>();
    result.setData(room);

    //  hold Result Put it in as an attribute 
    ResultDemo resultDemo = new ResultDemo();
    resultDemo.roomResult = result;

    //  Register a custom value extractor 
    Validator validator = ValidatorUtil.obtainValidatorFactory()
            .usingContext()
            .addValueExtractor(new ResultValueExtractor())
            .getValidator();
    ValidatorUtil.printViolations(validator.validate(resultDemo));
}

Run the test program , Output :

roomResult.finished Only for true, But your value is : false

It's perfect. Yes Result“ Containers ” The elements in are verified .

Tips: : This example is to put Result As Java Bean To test the properties of . In fact, in most cases, it is verified as the return value of the method . Similar way , Interested students can draw inferences by themselves

Here is a weak complement to , If in Spring Boot In the scene, you imagine doing this to Result< T > Provide support , Then you need to provide a verifier to cover the automatically assembled , May refer to ValidationAutoConfiguration.


Class level validation ( Multi field joint validation )

Constraints can also be placed at the class level ( In other words, annotations are marked on the class ). under these circumstances , The subject of validation is not a single attribute , It's the whole object . If verification depends on the correlation between several attributes of the object , Then class level constraints can do it all .

This requirement scenario is also very common in normal development , I'll give you a scenario :

Room It means a classroom ,maxStuNum Represents the maximum number of students allowed in the classroom ,studentNames The students in the classroom . It's obvious that there is such a rule here : The total number of students cannot be greater than the maximum allowed in the classroom , namely studentNames.size() <= maxStuNum. If you use transaction scripts to implement this validation rule , So your code must be interspersed with code like this :

if (room.getStudentNames().size() > room.getMaxStuNum()) {
    
    throw new RuntimeException("...");
}

Although this can also achieve the effect of verification , But obviously it's not elegant enough . Expect this case You can still use Bean Validation To achieve elegance , Now I'll take a walk .

Compared to the previous but field / The use of attribute validation case, What needs to be verified is the whole object ( Multiple fields ). Down here , I give two ways to implement it , For reference .


Mode one : Based on the built-in @ScriptAssert Realization

although Bean Validation There are no built-in class level annotations , but Hibernate-Validator But it provides an enhancement to this , To make up for its shortcomings [email protected] Namely HV Built in a very powerful 、 Can be used to validate annotations at the class level , It's easy to handle this kind of case:

@ScriptAssert(lang = "javascript", alias = "_", script = "_.maxStuNum >= _.studentNames.length")
@Data
public class Room {
    
    @Positive
    private int maxStuNum;
    @NotNull
    private List<String> studentNames;
}

@ScriptAssert Support to write scripts to complete the verification logic , What we use here is javascript( The only choice by default , It's also the default choice )

The test case :

public static void main(String[] args) {
    
    Room room = new Room();
    ValidatorUtil.printViolations(ValidatorUtil.obtainValidator().validate(room));
}

Run the program , Throw the wrong :

Caused by: <eval>:1 TypeError: Cannot get property "length" of null
	at jdk.nashorn.internal.runtime.ECMAErrors.error(ECMAErrors.java:57)
	at jdk.nashorn.internal.runtime.ECMAErrors.typeError(ECMAErrors.java:213)
	...

This error means _.studentNames The value is null, That is to say room.studentNames The value of the field is null.

what? It's not clearly marked on its head @NotNull Comments , How could it be for null Well ? This actually involves a little knowledge point mentioned above , Here's a word : All constraint annotations are executed , There is no short circuit effect ( Unless the verification program throws an exception ), As long as you dare to bid , I dare to carry out , So here's why it's wrong. You get it .

Tips: :@ScriptAssert Yes null Value is not immune to , In any case, it will carry out , Therefore, when writing scripts, pay attention to empty sentence

Of course , The execution of multiple constraints can also be sorted ( Orderly ), This involves the execution order of multiple constraints ( Sequence ) problem , For the time being, this paper bypasses . For example, fill in a value first , In the following special article, we will explain several constraint annotation execution sequence problems and case analysis .

Modify the test script ( Add a student , Let it not be null):

public static void main(String[] args) {
    
    Room room = new Room();
    room.setStudentNames(Collections.singletonList("DHY"));

    ValidatorUtil.printViolations(ValidatorUtil.obtainValidator().validate(room));
}

Run again :

 Execute script expression "_.maxStuNum >= _.studentNames.length" No return of expected results , But your value is : Room(maxStuNum=0, studentNames=[DHY])
maxStuNum It must be a positive number , But your value is : 0

The verification results are in line with the expectation :0(maxStuNum) < 1(studentNames.length).

Tips: : If you add a sentence to the test script room.setMaxStuNum(1);, So what's the result ?


Mode two : Custom annotation method to achieve

although BV Custom annotations have not been mentioned before , But it's not hard , So here's a face , You can also come back after reading the following article .

  • Customize a constraint annotation , And it provides the implementation of constraint logic
@Target({
    TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = {
    ValidStudentCountConstraintValidator.class})
public @interface ValidStudentCount {
    
    String message() default " The number of students exceeds the maximum ";
    Class<?>[] groups() default {
    };
    Class<? extends Payload>[] payload() default {
    };
}
public class ValidStudentCountConstraintValidator implements ConstraintValidator<ValidStudentCount, Room> {
    

    @Override
    public void initialize(ValidStudentCount constraintAnnotation) {
    
    }

    @Override
    public boolean isValid(Room room, ConstraintValidatorContext context) {
    
        if (room == null) {
    
            return true;
        }
        boolean isValid = false;
        if (room.getStudentNames().size() <= room.getMaxStuNum()) {
    
            isValid = true;
        }

        //  Custom prompt ( Of course, you can not customize , Then use the... In the note message Value of field )
        if (!isValid) {
    
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(" Check failed xxx")
                    .addPropertyNode("studentNames")
                    .addConstraintViolation();
        }
        return isValid;
    }
}

Write test scripts

public static void main(String[] args) {
    
    Room room = new Room();
    room.setStudentNames(Collections.singletonList("YourBatman"));

    ValidatorUtil.printViolations(ValidatorUtil.obtainValidator().validate(room));
}

Run the program , Output :

maxStuNum It must be a positive number , But your value is : 0
studentNames Check failed xxx, But your value is : Room(maxStuNum=0, studentNames=[YourBatman])

perfect , It's exactly as expected .

Both methods can implement class level validation , Both of them have their own advantages and disadvantages , It is mainly reflected in the following aspects :

  • @ScriptAssert It's built-in , So it's very convenient and versatile . But the disadvantage is that it is too general , So it's not semantically obvious , You need to read the script to know . Recommend a small amount of ( Non reusable )、 Use when the logic is simple
  • Custom annotation method . The disadvantage, of course, is “ Use it out of the box ” It's a little troublesome to get up , But it has the advantage of being semantically clear , Flexible and error free , Even complex verification logic can be easily handled

All in all , If your verification logic is only once ( Only one place to use ) And simple ( For example, it's just a simple judgment ), Recommended @ScriptAssert It's lighter . otherwise , You'll see ~


原网站

版权声明
本文为[Big flicker love flicker]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/205/202207240033268374.html