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.
validation
This 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 @Valid
to indicate the need for verification, controller
as follows:
@PostMapping
public HttpResponse demo(@Valid @RequestBody DemoDTO dto) {
return dto.getName();
}
Add another validation
note:
@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.
- Define unified return response class
- 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 MethodArgumentNotValidException
is 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 be
null
-
Scope of application:
Object
-
-
@NotNull
-
Note: The annotated element must not be
null
-
Scope of application:
Object
-
-
@AssertTrue
-
Note: The annotated element must be
true
-
Scope of application:
boolean
,Boolean
-
-
@AssertFalse
-
Note: The annotated element must be
false
-
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
,long
Long
-
-
@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
,long
Long
-
-
@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
,MinguoDate
ThaiBuddhistDate
-
-
@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
,MinguoDate
ThaiBuddhistDate
-
-
@Pattern(value)
-
Description: The annotated element must conform to the specified regular expression
- Scope of application:
CharSequence
,null
- Scope of application:
-
@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 {
};
}
@OverridesAttribute
Annotations 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
Age
represents the constraint annotation, and the latterInteger
represents 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
@Valid
If 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:
-
Based on the aop mechanism, the verified method needs to be registered as a component
-
Annotations can only be added to classes
@Validated
, not to individual methods. -
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 id
must 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:
- Can only be defined as an interface
- Inheritance is required
javax.validation.groups.Default
, otherwisegroups
other 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必填" }