This article is the author’s original, if you need to reprint the first famous address, public number reprint please apply for white.
Springboot-guide: A Spring Boot tutorial for beginners and experienced developers.
The importance of data verification goes without saying. Even in the case of data verification in the front end, we still need to verify the data passed to the back end again to avoid users bypassing the browser directly through some HTTP tools directly request some illegal data to the back end.
This article combined with their own experience in the actual use of the project, it can be said that the content of the article is very practical, do not know the friends can learn, can immediately practice to the project.
I’ll show you how to gracefully validate arguments in Java programs, especially Spring programs, using an example.
Infrastructure construction
Related,
If you’re developing normal Java programs, you might need to rely on something like this:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.9. The Final</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>2.2.6</version>
</dependency>
Copy the code
Using the Spring Boot program, spring-boot-starter-Web is enough, and its child dependencies contain everything we need. In addition to this dependency, lombok is used in the following demo, so don’t forget to add dependencies.
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Copy the code
Entity class
Here is the entity class used for the example.
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
@NotNull(message = "ClassId cannot be empty")
private String classId;
@Size(max = 33)
@NotNull(message = "Name cannot be empty")
private String name;
@Pattern(regexp = "((^Man$|^Woman$|^UGM$))", message = "Sex value is not optional")
@NotNull(message = "Sex cannot be empty.")
private String sex;
@Email(message = "Incorrect email format")
@NotNull(message = "Email cannot be empty")
private String email;
}
Copy the code
Regular expression description:
- ^ string: match begin with the string of a string, the string $: match the string at the end of the string - ^ string $: exact match string string - ((^ Man $| ^ Woman $| ^ UGM $)) : The value can only be Man,Woman, or UGMCopy the code
The following verification notes are from: www.cnkirito.moe/spring-vali… Thank you @xu Jingfeng.
Validation annotations provided by JSR:
@Null
The annotated element must be NULL@NotNull
The annotated element must not be NULL@AssertTrue
The annotated element must be true@AssertFalse
The annotated element must be false@Min(value)
The annotated element must be a number whose value must be greater than or equal to the specified minimum@Max(value)
The annotated element must be a number whose value must be less than or equal to the specified maximum@DecimalMin(value)
The annotated element must be a number whose value must be greater than or equal to the specified minimum@DecimalMax(value)
The annotated element must be a number whose value must be less than or equal to the specified maximum@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 and its value must be within an acceptable range@Past
The annotated element must be a past date@Future
The annotated element must be a future date@Pattern(regex=,flag=)
The annotated element must conform to the specified regular expression
Hibernate Validator provides validation annotations:
@NotBlank(message =)
Verify that the string is not null and must be greater than 0 in length@Email
The annotated element must be an E-mail address@Length(min=,max=)
The size of the annotated string must be within the specified range@NotEmpty
The value of the annotated string must be non-empty@Range(min=,max=,message=)
The annotated element must be in the appropriate scope
Validate the Controller’s input
Validate the RequestBody
Controller:
We need to verify the parameters with the @ Valid annotations, if validation fails, it throws MethodArgumentNotValidException. By default, Spring converts this exception to HTTP Status 400 (error request).
@RestController
@RequestMapping("/api")
public class PersonController {
@PostMapping("/person")
public ResponseEntity<Person> getPerson(@RequestBody @Valid Person person) {
returnResponseEntity.ok().body(person); }}Copy the code
ExceptionHandler:
Custom exception handlers help us catch exceptions and do some simple processing. If you don’t understand the code for handling exceptions below, check out the article common Ways SpringBoot handles exceptions.
@ControllerAdvice(assignableTypes = {PersonController.class})
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getAllErrors().forEach((error) -> {
String fieldName = ((FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
errors.put(fieldName, errorMessage);
});
returnResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors); }}Copy the code
Verified by tests:
I verify this with MockMvc, or you can verify it with a tool like Postman.
Let’s try to enter all the parameters correctly.
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class PersonControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
public void should_get_person_correctly(a) throws Exception {
Person person = new Person();
person.setName("SnailClimb");
person.setSex("Man");
person.setClassId("82938390");
person.setEmail("[email protected]");
mockMvc.perform(post("/api/person")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(objectMapper.writeValueAsString(person)))
.andExpect(MockMvcResultMatchers.jsonPath("name").value("SnailClimb"))
.andExpect(MockMvcResultMatchers.jsonPath("classId").value("82938390"))
.andExpect(MockMvcResultMatchers.jsonPath("sex").value("Man"))
.andExpect(MockMvcResultMatchers.jsonPath("email").value("[email protected]")); }}Copy the code
Verify that an exception is thrown in the case of an invalid parameter and can be caught correctly.
@Test
public void should_check_person_value(a) throws Exception {
Person person = new Person();
person.setSex("Man22");
person.setClassId("82938390");
person.setEmail("SnailClimb");
mockMvc.perform(post("/api/person")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(objectMapper.writeValueAsString(person)))
.andExpect(MockMvcResultMatchers.jsonPath("sex").value("Sex value is not optional"))
.andExpect(MockMvcResultMatchers.jsonPath("name").value("Name cannot be empty"))
.andExpect(MockMvcResultMatchers.jsonPath("email").value("Incorrect email format"));
}
Copy the code
The verification results using Postman are as follows:
Verify the Request Parameters (Path Variables and Request Parameters)
Controller:
Make sure, make sure you don’t forget to add to the classValidated
Annotated, this parameter tells Spring to validate method arguments.
@RestController
@RequestMapping("/api")
@Validated
public class PersonController {
@GetMapping("/person/{id}")
public ResponseEntity<Integer> getPersonByID(@Valid @PathVariable("id") @Max(value = 5,message = "Out of range of id") Integer id) {
return ResponseEntity.ok().body(id);
}
@PutMapping("/person")
public ResponseEntity<String> getPersonByName(@Valid @RequestParam("name") @Size(max = 6,message = "Out of range of name") String name) {
returnResponseEntity.ok().body(name); }}Copy the code
ExceptionHandler:
@ExceptionHandler(ConstraintViolationException.class)
ResponseEntity<String> handleConstraintViolationException(ConstraintViolationException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage());
}
Copy the code
Verified by tests:
@Test
public void should_check_param_value(a) throws Exception {
mockMvc.perform(get("/api/person/6")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isBadRequest())
.andExpect(content().string("Getpersonbyid. id: Out of id range"));
}
@Test
public void should_check_param_value2(a) throws Exception {
mockMvc.perform(put("/api/person")
.param("name"."snailclimbsnailclimb")
.contentType(MediaType.APPLICATION_JSON_UTF8))
.andExpect(status().isBadRequest())
.andExpect(content().string("Getpersonbyname.name: Beyond the scope of name"));
}
Copy the code
Verify the methods in the Service
We can also validate the input of any Spring component, rather than controller level input, using a combination of the @validated and @valid annotations.
Make sure, make sure you don’t forget to add to the classValidated
Annotated, this parameter tells Spring to validate method arguments.
@Service
@Validated
public class PersonService {
public void validatePerson(@Valid Person person){
// do something}}Copy the code
Verified by tests:
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class PersonServiceTest {
@Autowired
private PersonService service;
@Test(expected = ConstraintViolationException.class)
public void should_throw_exception_when_person_is_not_valid(a) {
Person person = new Person();
person.setSex("Man22");
person.setClassId("82938390");
person.setEmail("SnailClimb"); service.validatePerson(person); }}Copy the code
Manual parameter verification in Validator programming mode
Manual verification and verification results may be required in some scenarios.
@Test
public void check_person_manually(a) {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Person person = new Person();
person.setSex("Man22");
person.setClassId("82938390");
person.setEmail("SnailClimb");
Set<ConstraintViolation<Person>> violations = validator.validate(person);
//output:
// The email format is incorrect
// Name cannot be empty
// The sex value is not in the optional range
for(ConstraintViolation<Person> constraintViolation : violations) { System.out.println(constraintViolation.getMessage()); }}Copy the code
Here’s an example of a Validator from the Validator factory class, but you can also inject it directly via @AutoWired. But if you use this approach in non-Spring Component classes, you can only get the Validator through the factory class.
@Autowired
Validator validate
Copy the code
Custom Validator(practical)
If the built-in validation annotations don’t meet your needs, you can also customize the implementation annotations.
Example 1: Verify that the value of a particular field is in the optional range
For example, we now have a requirement that the Person class has a region field. The region field can only be one of China, China-Taiwan, or China-Hongkong.
The first step is to create an annotation:
@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = RegionValidator.class)
@Documented
public @interface Region {
String message(a) default"Region value not in optional range"; Class<? >[] groups()default {};
Class<? extends Payload>[] payload() default {};
}
Copy the code
Second, you need to implement the ConstraintValidator interface and rewrite the isValid method:
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
public class RegionValidator implements ConstraintValidator<Region.String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
HashSet<Object> regions = new HashSet<>();
regions.add("China");
regions.add("China-Taiwan");
regions.add("China-HongKong");
returnregions.contains(value); }}Copy the code
Now you can use this annotation:
@Region
private String region;
Copy the code
Case 2: Checking phone numbers
To verify that our phone number is valid, we can use regular expressions, which can be found online. You can even search for regular expressions that are specific to a particular carrier’s phone number segment.
PhoneNumber.java
import javax.validation.Constraint;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Constraint(validatedBy = PhoneNumberValidator.class)
@Target({FIELD, PARAMETER})
@Retention(RUNTIME)
public @interface PhoneNumber {
String message(a) default "Invalid phone number";
Class[] groups() default {};
Class[] payload() default {};
}
Copy the code
PhoneNumberValidator.java
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber.String> {
@Override
public boolean isValid(String phoneField, ConstraintValidatorContext context) {
if (phoneField == null) {
// can be null
return true;
}
return phoneField.matches("^1(3[0-9]|4[57]|5[0-35-9]|8[0-9]|70)\\d{8}$") && phoneField.length() > 8 && phoneField.length() < 14; }}Copy the code
Done, we are now ready to use this annotation.
@PhoneNumber(message = "PhoneNumber format is incorrect")
@NotNull(message = "PhoneNumber cannot be empty")
private String phoneNumber;
Copy the code
Using validation Groups
Some scenarios we need to use the validation group, say so may not too clear, say simple point is the object of different operation methods have different validation rules, the sample is as follows (this is my current project experience using less, because itself level to understand in the code is more troublesome, then and more trouble to write).
Create two interfaces:
public interface AddPersonGroup {}public interface DeletePersonGroup {}Copy the code
We can use validation groups in this way
@NotNull(groups = DeletePersonGroup.class)
@Null(groups = AddPersonGroup.class)
private String group;
Copy the code
@Service
@Validated
public class PersonService {
public void validatePerson(@Valid Person person) {
// do something
}
@Validated(AddPersonGroup.class)
public void validatePersonGroupForAdd(@Valid Person person) {
// do something
}
@Validated(DeletePersonGroup.class)
public void validatePersonGroupForDelete(@Valid Person person) {
// do something}}Copy the code
Verified by tests:
@Test(expected = ConstraintViolationException.class)
public void should_check_person_with_groups(a) {
Person person = new Person();
person.setSex("Man22");
person.setClassId("82938390");
person.setEmail("SnailClimb");
person.setGroup("group1");
service.validatePersonGroupForAdd(person);
}
@Test(expected = ConstraintViolationException.class)
public void should_check_person_with_groups2(a) {
Person person = new Person();
person.setSex("Man22");
person.setClassId("82938390");
person.setEmail("SnailClimb");
service.validatePersonGroupForDelete(person);
}
Copy the code
Be careful when using validation groups. This is an anti-pattern and can make your code less logical.
Code address: github.com/Snailclimb/…
@NotNull
vs @Column(nullable = false)
(important)
@column (Nullable = false) is not the same as @notnull when using JPA to manipulate data. It’s important to figure this out!
@NotNull
Is the JSR 303 Bean validation annotation, which has nothing to do with the database constraint itself.@Column(nullable = false)
: is a method that JPA declares as non-null.
In summary, the former is used for validation, while the latter is used to indicate constraints on the table when the database creates the table.
TODO
- The principle of analysis
reference
- Reflectoring. IO/bean – valida…
- www.cnkirito.moe/spring-vali…
Open Source Project Recommendation
Other open source projects recommended by the authors:
- JavaGuide: A Java learning + Interview Guide that covers the core knowledge that most Java programmers need to master.
- Springboot-guide: A Spring Boot tutorial for beginners and experienced developers.
- Advancer-advancement: I think there are some good habits that technical people should have!
- Spring-security-jwt-guide: Start from Scratch! Spring Security With JWT (including permission validation) backend part of the code.