API development often encountered some validation of the request data, at this time if the use of annotations there are two benefits, one is the validation logic and business logic separation, code clear, and the second is the validation logic can be easily reused, just need to verify the place to add annotations.
Java provides some basic validation annotations, such as, @NotNull
@Size
but more generally, the need for custom validation logic, which allows you to implement a validation note on your own, which is simple and requires only two things:
- A custom annotation, and specifies the validator
- Implementation of a validator
Custom validation Annotations
Consider an API that receives an Student
object and expects the value of the field in the object to be an age
odd number, so that the following annotations can be created:
@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy = AgeValidator.class)public @interface Odd { String message() default "Age Must Be Odd"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {};}
which
@Target
Indicates where this annotation is to be used, such as object, domain, constructor, and so on, because it is age
on the domain, so chooseFIELD
@Retention
Indicates the lifetime of the annotations, which can be SOURCE
(stored only in the source code, discarded by the compiler), CLASS
(available in the class file, discarded by the VM), and RUNTIME
(also retained at runtime), where the longest life cycle is selected.RUNTIME
@Constraint
Is the most critical, it indicates that the annotation is a validation note and that a validator is specified that implements the validation logic
message()
Indicates the message returned after a validation failure, as required by this method @Constraint
groups()
And payload()
also for @Constraint
requirements, can default to empty, detailed use can view the @Constraint
document
Creating validators
With annotations, you need a validator to implement the validation logic:
public class AgeValidator implements ConstraintValidator<Odd,Integer> { @Override public void initialize(Odd constraintAnnotation) { } @Override public boolean isValid(Integer age, ConstraintValidatorContext constraintValidatorContext) { return age % 2 != 0; }}
which
- The validator has two type parameters, the first one is the annotation to which it belongs, the second is the type of place where the annotation is acting, because of the effect
age
, so here is theInteger
initialize()
You can invoke the methods in the annotations before the validation begins to get the parameters in some annotations, which are not used here.
isValid()
Is the place to judge whether it's legal.
Application Notes
Once the annotations and validators have been created, you can use annotations:
public class Student { @Odd private int age; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }}
@RestControllerpublic class StudentResource { @PostMapping("/student") public String addStudent(@Valid @RequestBody Student student) { return "Student Created"; }}
Add annotations where validation is required @Valid
, and if the age in the request Student
is not odd, a response is given 400
:
{ "timestamp": "2018-08-15T17:01:44.598+0000", "status": 400, "error": "Bad Request", "errors": [ { "codes": [ "Odd.student.age", "Odd.age", "Odd.int", "Odd" ], "arguments": [ { "codes": [ "student.age", "age" ], "arguments": null, "defaultMessage": "age", "code": "age" } ], "defaultMessage": "Age Must Be Odd", "objectName": "student", "field": "age", "rejectedValue": 12, "bindingFailure": false, "code": "Odd" } ], "message": "Validation failed for object=‘student‘. Error count: 1", "path": "/student"}
You can also manually handle errors, plus one BindingResult
to receive validation results:
@RestControllerpublic class StudentResource { @PostMapping("/student") public String addStudent(@Valid @RequestBody Student student, BindingResult validateResult) { if (validateResult.hasErrors()) { return validateResult.getAllErrors().get(0).getDefaultMessage(); } return "Student Created"; }}
At this point, if the validation goes wrong, only a response with the status of must be odd is returned 200
.
Custom annotations in Java for data validation