Article catalogue
Pre
SpringBoot - elegant implementation of [parameter verification] advanced
SpringBoot - elegant implementation of [custom parameter verification] advanced
SpringBoot - elegant implementation of [parameter grouping verification] advanced
SpringBoot - use Assert validation to make business code more concise
In development, in order to ensure the stability and security of the interface, verification is generally required in the interface logic. For example, the above articles are all about parameter verification. Generally, we use the Bean Validation verification framework.
Verification rules | Rule description |
---|---|
@Null | Restriction can only be null |
@NotNull | Restriction must not be null |
@AssertFalse | Restriction must be false |
@AssertTrue | Restriction must be true |
@DecimalMax(value) | The limit must be a number no greater than the specified value |
@DecimalMin(value) | The limit must be a number not less than the specified value |
@Digits(integer,fraction) | The limit must be one decimal, and the number of digits in the integer part cannot exceed integer, and the number of digits in the decimal part cannot exceed fraction |
@Future | Limit must be a future date |
@Max(value) | The limit must be a number no greater than the specified value |
@Min(value) | The limit must be a number not less than the specified value |
@Past | Verify that the element value (date type) of the annotation is earlier than the current time |
@Pattern(value) | The restriction must conform to the specified regular expression |
@Size(max,min) | The limit character length must be between min and max |
@NotEmpty | The element value of the validation annotation is not null and empty (string length is not 0, collection size is not 0) |
@NotBlank | The element value of the verification annotation is not null (it is not null, and the length is 0 after removing the first space). Unlike @ NotEmpty, @ NotBlank is only applied to strings, and the spaces of strings will be removed during comparison |
Verify that the element value of the annotation is email. You can also specify a custom email format through regular expression and flag |
[business rule verification] in most cases, if else is used for simplicity. How can we play more gracefully?
Tips: refer to the standard method of Bean Validation and verify business rules with the help of user-defined verification annotations
demand
- New user, user name + mobile number + unique email address
- Modify the user. The modified [user name + mobile phone number + email] cannot conflict with the user information in the library
Realization Trilogy
Of course, the simple writing is the whole if else return. Check the DB and make a judgment. It looks a little different later today
Entity class
package com.artisan.bean; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.validator.constraints.Length; import javax.validation.constraints.Email; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotEmpty; /** * @author Small craftsman * @version 1.0 * @mark: show me the code , change the world */ @Data @AllArgsConstructor @NoArgsConstructor public class Artisan { private String id; @NotEmpty(message = "Code Cannot be empty") private String code; @NotBlank(message = "First name is required") private String name; @Length(min = 8, max = 12, message = "password The length must be between 8 and 12") private String password; @Email(message = "Please fill in the correct email address") private String email; private String sex; private String phone; }
Step 1 two custom annotations
Create two user-defined annotations for business rule verification
package com.artisan.annos; import com.artisan.validate.ArtisanValidator; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; /** * * Customize the "user unique" verification annotation Only include ----------- > user name + mobile phone number + email * @author artisan */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE}) @Constraint(validatedBy = ArtisanValidator.UniqueArtisanValidator.class) public @interface UniqueArtisan { String message() default "User name, mobile phone number and email address cannot be duplicate with existing users"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
package com.artisan.annos; import com.artisan.validate.ArtisanValidator; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.*; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Indicates that a user's information is conflict free * ""No conflict" means that the user's sensitive information does not coincide with other users, such as changing the email and mobile phone of a registered user to a value consistent with another existing registered user * @author artisan */ @Documented @Retention(RUNTIME) @Target({FIELD, METHOD, PARAMETER, TYPE}) @Constraint(validatedBy = ArtisanValidator.NotConflictArtisanValidator.class) public @interface NotConflictArtisan { String message() default "The user name, email address and mobile phone number are duplicate with existing users"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
Step 2 user defined verifier
package com.artisan.validate; import com.artisan.annos.NotConflictArtisan; import com.artisan.annos.UniqueArtisan; import com.artisan.bean.Artisan; import com.artisan.repository.ArtisanDao; import lombok.extern.slf4j.Slf4j; import javax.annotation.Resource; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.lang.annotation.Annotation; import java.util.Collection; import java.util.function.Predicate; /** * @author Small craftsman * @version 1.0 * @mark: show me the code , change the world */ @Slf4j public class ArtisanValidator<T extends Annotation> implements ConstraintValidator<T, Artisan> { protected Predicate<Artisan> predicate = c -> true; @Resource protected ArtisanDao artisanDao; @Override public boolean isValid(Artisan artisan, ConstraintValidatorContext constraintValidatorContext) { return artisanDao == null || predicate.test(artisan); } /** * Verify whether the user is unique * That is, judge whether there is information of the current new user in the database, such as user name, mobile phone and email */ public static class UniqueArtisanValidator extends ArtisanValidator<UniqueArtisan> { @Override public void initialize(UniqueArtisan uniqueArtisan) { predicate = c -> !artisanDao.existsByNameOrEmailOrPhone(c.getName(), c.getEmail(), c.getPhone()); } } /** * Verify whether it conflicts with other users * Changing the user name, e-mail and phone number to one that does not duplicate the existing one or only repeats it with yourself is not a conflict */ public static class NotConflictArtisanValidator extends ArtisanValidator<NotConflictArtisan> { @Override public void initialize(NotConflictArtisan notConflictUser) { predicate = c -> { log.info("user detail is {}", c); Collection<Artisan> collection = artisanDao.findByNameOrEmailOrPhone(c.getName(), c.getEmail(), c.getPhone()); // Changing the user name, e-mail and phone number to one that does not duplicate the existing one or only repeats it with yourself is not a conflict return collection.isEmpty() || (collection.size() == 1 && collection.iterator().next().getId().equals(c.getId())); }; } } }
Custom validation annotations need to implement the ConstraintValidator interface.
- The first parameter is the custom annotation type
- The second parameter is the class of the annotated field, because multiple parameters need to be verified and directly passed into the user object.
It should be mentioned that the implementation class of the ConstraintValidator interface does not need to add @ Component, which has been loaded into the container at startup.
Use the predict functional interface to judge business rules
Step 3 verification
package com.artisan.controller; import com.artisan.annos.NotConflictArtisan; import com.artisan.annos.UniqueArtisan; import com.artisan.bean.Artisan; import com.artisan.repository.ArtisanDao; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; /** * */ @RestController @RequestMapping("/buziVa/artisan") @Slf4j @Validated public class ArtisanController { @Autowired private ArtisanDao artisanDao; // POST method @PostMapping public Artisan createUser(@UniqueArtisan @Valid Artisan user) { Artisan savedUser = artisanDao.save(user); log.info("save user id is {}", savedUser.getId()); return savedUser; } // PUT @SneakyThrows @PutMapping public Artisan updateUser(@NotConflictArtisan @Valid @RequestBody Artisan artisan) { Artisan editUser = artisanDao.save(artisan); log.info("update artisan is {}", editUser); return editUser; } }
You only need to add custom annotations to the method, and there is no need to add any business rule code to the business logic.
Summary
Through the above steps, business verification is completely separated from business logic. When verification is needed, it is automatically triggered with @ Validated annotation or manually triggered by code.
These annotations apply to any level of code, such as controller, service layer, persistence layer, etc.
During development, the format verification annotation without business meaning can be placed on the Bean's class definition, and the verification with business logic can be placed outside the Bean's class definition.
The difference is that the annotation placed in the class definition can run automatically, while it can run only when the @ Validated annotation is clearly marked outside the class.