Custom annotation to realize logical verification

Time:2022-5-17

In daily interface development, in order to ensure the stability and security of the interface, we generally need to deal with two kinds of verification in the interface logic:

  1. Parameter verification
  2. Business rule verification

First, let’s look at parameter verification.

1、 Parameter verification

Parameter verification is easy to understand. For example, when logging in, you need to verify whether the user name and password are empty, and when creating a user, you need to verify whether the format of e-mail and mobile phone number is accurate.

The implementation of parameter verification is also very simple. We only need to useBean ValidationThe verification framework is enough. With the verification annotation provided by it, we can easily complete parameter verification.

Common verification notes include:

@Null、@NotNull、@AssertTrue、
@AssertFalse、@Min、@Max、
@DecimalMin、@DecimalMax、
@Negative、@NegativeOrZero、
@Positive、@PositiveOrZero、
@Size、@Digits、@Past、
@PastOrPresent、@Future、@FutureOrPresent、
@Pattern、@NotEmpty、@NotBlank、@Email

Next, let’s look at business rule verification.

2、 Business rule verification

For example, the uniqueness of the business user’s name and number in the database cannot be guaranteed to meet the needs of any other user’s business.

This requiresWhen creating a user, you need to verify whether the user name, mobile phone number and email address are registeredWhen editing a user, you cannot change the information to the attribute of an existing user

95% of programmers often choose to write in the service logic when facing this kind of business rule verification. The common code logic is as follows:

public void create(User user) {
    Account account = accountDao.queryByUserNameOrPhoneOrEmail(user.getName(),user.getPhone(),user.getEmail());
    if (account != null) {
        Throw new illegalargumentexception ("user already exists, please re-enter");
    }
}

Although I introduced in my last article that using assert to optimize the code can make it look more concise, it’s a programmer’s version of the story of paying back pearls by giving simple validation to bean validation and leaving complex validation to myself.

The most elegant implementation method should refer to the standard method of bean validation and complete the verification of business rules with the help of custom verification annotations.

Next, we complete the verification of business rules through user-defined annotations through the user interface case mentioned above.

3、 Code practice

The requirements are easy to understand. When registering a new user, it should be constrained not to duplicate the key information of any existing user; When modifying your own information, you can only duplicate your own information, and you are not allowed to modify it into the information of existing users.

These constraint rules not only serve these two methods, but may be used in other entries in user resources and even in other layered code. Checking on the bean can completely cover these usage scenarios.

3.1 user defined annotation

First, we need to create two user-defined annotations for business rule verification:

  • UniqueUser: indicates that a user is unique. Uniqueness includes: user name, mobile phone number and email address
@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
@Constraint(validatedBy = UserValidation.UniqueUserValidator.class)
public @interface UniqueUser {

    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 {};
}
  • NotConflictUser: indicates that a user’s information is conflict free. Conflict free means that the user’s sensitive information does not coincide with other users
@Documented
@Retention(RUNTIME)
@Target({FIELD, METHOD, PARAMETER, TYPE})
@Constraint(validatedBy = UserValidation.NotConflictUserValidator.class)
public @interface NotConflictUser {
    String message() default "user name, email address and mobile phone number are duplicate with existing users";

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

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

3.2 implement business verification rules

For the custom validation annotation to take effect, you need to implementConstraintValidatorInterface. The first parameter of the interface isCustom annotation type, the second parameter isClass of annotated field, because multiple parameters need to be verified, we directly pass in the user object. One thing to mention isConstraintValidatorThe implementation class of the interface does not need to be added@ComponentIt is already loaded into the container at startup.

@Slf4j
public class UserValidation<T extends Annotation> implements ConstraintValidator<T, User> {

    protected Predicate<User> predicate = c -> true;

    @Resource
    protected UserRepository userRepository;

    @Override
    public boolean isValid(User user, ConstraintValidatorContext constraintValidatorContext) {
        return userRepository == null || predicate.test(user);
    }

    /**
     *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 UniqueUserValidator extends UserValidation<UniqueUser>{
        @Override
        public void initialize(UniqueUser uniqueUser) {
            predicate = c -> !userRepository.existsByUserNameOrEmailOrTelphone(c.getUserName(),c.getEmail(),c.getTelphone());
        }
    }

    /**
     *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 NotConflictUserValidator extends UserValidation<NotConflictUser>{
        @Override
        public void initialize(NotConflictUser notConflictUser) {
            predicate = c -> {
                log.info("user detail is {}",c);
                Collection<User> collection = userRepository.findByUserNameOrEmailOrTelphone(c.getUserName(), c.getEmail(), c.getTelphone());
                //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()));
            };
        }
    }

}

Here, the predict functional interface is used to judge the business rules.

3.3 use

@RestController
@RequestMapping("/senior/user")
@Slf4j
@Validated
public class UserController {
    @Autowired
    private UserRepository userRepository;

    @PostMapping
    public User createUser(@UniqueUser @Valid User user){
        User savedUser = userRepository.save(user);
        log.info("save user id is {}",savedUser.getId());
        return savedUser;
    }

    @SneakyThrows
    @PutMapping
    public User updateUser(@NotConflictUser @Valid @RequestBody User user){
        User editUser = userRepository.save(user);
        log.info("update user is {}",editUser);
        return editUser;
    }
}

It is easy to use. You only need to add custom annotations to the method. There is no need to add any business rule code to the business logic.

3.4 testing

The following error occurs after calling the interface, indicating that the business rule verification is effective.

{
  "status": 400,
  "Message": "user name, mobile phone number and mailbox cannot be duplicated with existing users",
  "data": null,
  "timestamp": 1644309081037
}

Summary

Through the above steps, business verification is completely separated from business logic, which can be used when verification is needed@ValidatedAnnotations are triggered automatically or manually by code. According to the requirements of your project, these annotations can be applied to the code of any level, such as controller, service layer, persistence layer and so on.

This method is more elegant than any business rule verification method. It is recommended to be used in the project. 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 between the two is that the annotations placed in the class definition can run automatically, while the annotations placed outside the class need to be clearly marked as in the previous code.