Spring boot elegant parameter verification

Spring boot elegant parameter verification

Recently, in the projects of other business groups of the company, I found that many places use if else to check request parameters. Almost every place that receives parameters has a lot of parameter checks.

validationThis article introduces how to use elegant verification of request parameters in the spring boot project.

Assume that we have done all the preliminary work, and now there is a new interface that needs to verify parameters.

First add an annotation in front of the interface parameters @Validto indicate the need for verification, controlleras follows:

    @PostMapping
    public HttpResponse demo(@Valid @RequestBody DemoDTO dto) {
    
    
        return dto.getName();
    }

Add another validationnote:

@NotNull(message = "请输入姓名") 
private String name;

Okay, the output of our call is as follows:

{ "code": 500, "msg": "请输入姓名" }

Isn't it very simple? You only need to add two annotations, one annotation indicating the need for verification, and one annotation indicating how to verify, and everything is done.

But before that, some other work needs to be done.

  1. Define unified return response class
  2. Interception exception

We first define a class that encapsulates the response HttpResponse:

	@Setter
    @Getter
    @Builder
    public class HttpResponse {
    
    
        private Integer code;
        private String msg;
    }

Then intercept the exception globally and return this object. The complete exception handling will be discussed later. Here I will just show you how to configure it:

    @ControllerAdvice
    public class BaseExceptionHandler {
    
    
        @ResponseBody
        @ExceptionHandler(MethodArgumentNotValidException.class)
        public HttpResponse handle(MethodArgumentNotValidException e) {
    
    
            FieldError fieldError = e.getBindingResult().getFieldError();
            return HttpResponse.builder().code(500).msg(fieldError.getDefaultMessage()).build();
        }
    }

The one here MethodArgumentNotValidExceptionis thrown during verification. In fact, other exceptions will be thrown because of verification failure. However, these are generally caused by problems during the development process and have nothing to do with user input, so "system exceptions" can be responded to in a unified manner. ", no need to deal with it separately.

There are other ways to handle exceptions, but unified interception is the easiest one. During the development process, there is no need to consider how to handle verification failures. You only need to write a few annotations to set the rules.

Introduction to Bean Validation

Bean Validation is a set of specifications on data validation, JSR 303 – Bean Validation specification jcp.org/en/jsr/deta…

Bean Validation defines a series of metadata models and APIs. Hibernate Validator is the reference implementation of Bean Validation. In addition to the built-in constraints in the JSR 303 specification, it also defines some additional commonly used constraint implementations. www.hibernate.org/subprojects…

Constraints in Bean Validation

constraint illustrate
@Null The annotated element must benull
@NotNull The annotated element must not benull
@AssertTrue The annotated element must betrue
@AssertFalse The annotated element must befalse
@Min(value) The annotated element must be a number,
Its value must be greater than or equal to the specified minimum value
@Max(value) The annotated element must be a number,
Its value must be less than or equal to the specified maximum value
@DecimalMin(value) The annotated element must be a number,
Its value must be greater than or equal to the specified minimum value
@DecimalMax(value) The annotated element must be a number,
Its value must be less than or equal to the specified maximum value
@Size(max, min) The size of the annotated element must be within the specified range
@Digits (integer, fraction) The annotated element must be a number,
Its value must be within the acceptable range
@Past The annotated element must be a date in the past
@Future The annotated element must be a future date
@Pattern(value) The annotated element must match the specified regular expression

Hibernate Validator additional constraints

constraint illustrate
@Email The annotated element must be an email address
@Length The size of the annotated string must be within the specified range
@NotEmpty The commented string must be non-empty
@Range The annotated element must be within the appropriate scope

A constraint usually consists of an annotation and a corresponding constraint validator, which have a one-to-many relationship. That is to say, there can be multiple constraint validators corresponding to one annotation. At runtime, the Bean Validation framework itself will select the appropriate constraint validator to validate the data based on the type of the annotated element.

Sometimes, more complex constraints are needed in the user's application. Bean Validation provides a mechanism for extending constraints. This can be achieved in two ways, one is to combine existing constraints to generate a more complex constraint, and the other is to develop a completely new constraint. I will demonstrate how to add a custom validator later.

Scope of application of constraints

  • @Null

    • Note: The annotated element must benull

    • Scope of application:Object

  • @NotNull

    • Note: The annotated element must not benull

    • Scope of application:Object

  • @AssertTrue

    • Note: The annotated element must betrue

    • Scope of application: boolean,Boolean

  • @AssertFalse

    • Note: The annotated element must befalse

    • Scope of application: boolean,Boolean

  • @Min(value)

    • Description: The annotated element must be a number, and its value must be greater than or equal to the specified minimum value

    • Scope of application: BigDecimal, BigInteger, byte, Byte, short, Short, int, Integer, long,Long

  • @Max(value)

    • Description: The annotated element must be a number, and its value must be less than or equal to the specified maximum value

    • Scope of application: BigDecimal, BigInteger, byte, Byte, short, Short, int, Integer, long,Long

  • @DecimalMin(value)

    • Description: The annotated element must be a number, and its value must be greater than or equal to the specified minimum value

    • Scope of application: BigDecimal, BigInteger, CharSequence, byte, Byte, short, , Short, int, Integer,longLong

  • @DecimalMax(value)

    • Description: The annotated element must be a number, and its value must be less than or equal to the specified maximum value

    • Scope of application: BigDecimal, BigInteger, CharSequence, byte, Byte, short, , Short, int, Integer,longLong

  • @Size(max, min)

    • Note: The size of the annotated element must be within the specified range

    • Scope of application: CharSequence, Collection, Map,Array

  • @Digits (integer, fraction)

    • Note: The annotated element must be a number and its value must be within an acceptable range

    • Scope of application: BigDecimal, BigInteger, CharSequence, byte Byte, short Short, int Integer,long Long

  • @Past

    • Note: The annotated element must be a past date

    • Scope of application: Date, Calendar, Instant, LocalDate, LocalDateTime, , LocalTime, MonthDay, OffsetDateTime, OffsetTime, Year, YearMonth, ZonedDateTime, HijrahDate, JapaneseDate,MinguoDateThaiBuddhistDate

  • @Future

    • Note: The annotated element must be a future date

    • Scope of application: Date, Calendar, Instant, LocalDate, LocalDateTime, , LocalTime, MonthDay, OffsetDateTime, OffsetTime, Year, YearMonth, ZonedDateTime, HijrahDate, JapaneseDate,MinguoDateThaiBuddhistDate

  • @Pattern(value)

  • Description: The annotated element must conform to the specified regular expression

    • Scope of application: CharSequence,null
  • @Email

    • Note: The annotated element must be an email address
    • Scope of application:CharSequence
  • @Length

    • Description: The size of the annotated string must be within the specified range
    • Scope of application:
  • @NotEmpty

    • Note: The commented string must be non-empty
    • Scope of application:
  • @Range

    • Note: The annotated element must be within the appropriate range
    • Scope of application:

Use code verification

ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); Validator validator = factory.getValidator(); Set<ConstraintViolation<DemoDTO>> violations = validator.validate(dto);

In fact, spring boot has already been defined and can be relied on directly:

@Resource 
private Validator validator;

Custom constraints

The built-in constraints are generally sufficient. If there are unusual situations, you need to define the constraints yourself.

Combine other constraints

Directly add the original constraints to your own annotations. We add a new annotation to constrain the age attribute. If we restrict the age to be between 0-150:

    @Max(150)
    @Min(0)
    @Target({
    
    METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
    @Retention(RUNTIME)
    @Documented
    @Constraint(validatedBy = {
    
    })
    public @interface Age {
    
    
        @OverridesAttribute(constraint = Max.class, name = "message") @OverridesAttribute(constraint = Min.class, name = "message") String message() default "年龄超出范围";

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

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

@OverridesAttributeAnnotations can override some attributes of combined constraints. Here we only cover message. Other attributes can also be overridden.

Just test it briefly:

@Age(message = "这年龄不太对吧")
private Integer age;

Output:

{ "code": 500, "msg": "这年龄不太对吧" }

New constraint

It’s still an example of restricting age. This time we write the logic ourselves, starting with the constraint annotation:

    @Target({
    
    METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
    @Retention(RUNTIME)
    @Documented
    @Constraint(validatedBy = AgeValidtor.class)
    public @interface Age {
    
    
        String message() default "年龄超出范围";

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

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

Note here @Constraint(validatedBy = AgeValidtor.class), it sets the validator we implemented ourselves

Next is to implement the validator:

    public class AgeValidtor implements ConstraintValidator<Age, Integer> {
    
    
        @Override
        public boolean isValid(Integer value, ConstraintValidatorContext context) {
    
    
            return value == null || (value >= 0 && value <= 150);
        }
    }

There are two generic types of the interface implemented by the validator. The former Agerepresents the constraint annotation, and the latter Integerrepresents the validator used to verify the attributes of this type. As mentioned earlier, a constraint annotation can be associated with multiple validators, depending on the type of attribute to be verified. Choose the appropriate validator.

The output is still the same:

{ "code": 500, "msg": "这年龄不太对吧" }

Nested validation

@ValidIf there is another object inside an object that needs to be verified, you need to add annotations above the properties of this object.

    @Getter
    @Setter
    @ToString
    public class DemoDTO {
    
    
        @Valid
        private InlineObject inlineObject;
    }

Ordinary method to check parameters

In addition to verifying interfaces, we can also verify ordinary methods. First, add annotations to the class where the method that needs to be verified is located @Validated:

	@Validated
    @Service
    public class DemoService {
    
    
        public void demo(@Valid DemoDTO dto) {
    
    
        }
    }

Return after normal call:

{ "code": 500, "msg": "这年龄不太对吧" }

Notice:

  1. Based on the aop mechanism, the verified method needs to be registered as a component

  2. Annotations can only be added to classes @Validated, not to individual methods.

  3. The exception thrown is ConstraintViolationException, which needs to be intercepted separately. Example:

	@ResponseBody
    @ExceptionHandler(ConstraintViolationException.class)
    public HttpResponse handle(ConstraintViolationException e) {
    
    
        return HttpResponse.builder().code(500).msg(e.getConstraintViolations().stream().findFirst().map(ConstraintViolation::getMessage).orElse("参数校验失败")).build();
    }

Group verification

Many times, a class is used for adding and updating, but the attribute idmust not be empty when updating, and must be empty when adding, or it does not matter what it is when adding.

At this time, you need to use different verification rules according to the situation. First define the two situation groups of new and updated:

public interface ValidationGroup {
    
    
        interface Create extends Default {
    
    
        }

        interface Update extends Default {
    
    
        }
    }

Notice:

  1. Can only be defined as an interface
  2. Inheritance is required javax.validation.groups.Default, otherwise groupsother constraints that are not added will be regarded as other groups.

Next, define the attributes that need to be verified in two cases:

	@NotNull(message = "更新时id必填", groups = ValidationGroup.Update.class)
    private Integer id;
    @NotNull(message = "新增时name必填", groups = ValidationGroup.Create.class)
    private String name;
    @NotNull(message = "任何情况age必填")
    private Integer age;

Define the interfaces for two situations:

@PostMapping("/create")
    public String create(@Validated(ValidationGroup.Create.class) @RequestBody DemoDTO dto) {
    
    
        return dto.getName();
    }

    @PostMapping("/update")
    public String update(@Validated(ValidationGroup.Update.class) @RequestBody DemoDTO dto) {
    
    
        return dto.getName();
    }

At this time, we use fixed input parameters to call the two interfaces respectively. The parameters are:

{ "age": 12 }

Output in two cases:

{ "code": 500, "msg": "新增时name必填" }

{ "code": 500, "msg": "更新时id必填" }

Guess you like

Origin blog.csdn.net/asd54090/article/details/131598453