preface

We usually write interfaces using the @valid annotation, which simply validates our interface input arguments with @notnull, @notempty, etc.

However, some input parameters need to query the database. In this case, the validation annotation provided by @VALID does not meet the requirements.

So let’s take a look at some of the “slut operations” of pre-validation optimization

Without further ado, go to the code.

scenario

We often do some pre-condition checks at the Service layer, such as whether the user exists, as shown in the following code:

public void create(UserCreateDTO dto) {
  	 
    // ----> Prejudgment
  	User existUsername = userRepository.findByName(dto.getName());
    Fire.checkNotNull(existUsername, GlobalErrorCode.USER_NAME_IS_EXIST);
    // <----
  
    / /... Omit the other
    User user = new User();
    user.setName(dto.getName());
    user.setEmail(dto.getEmail());
    userRepository.save(user);
}
Copy the code

A simple check is made to see if Username exists, where Fire is the exception wrapper class and if existUsername is empty, an exception is thrown.

A simple optimization

public void create(UserCreateDTO dto) {
  	 
    // ----> Prejudgment
  	usernameValidate.validate(dto.getName());
    // <----
  
    / /... Omit the other
    User user = new User();
    user.setName(dto.getName());
    user.setEmail(dto.getEmail());
    userRepository.save(user);
}

@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class UsernameValidate {

    private final UserRepository userRepository;

    public void validate(String name) { User existUsername = userRepository.findByName(name); Fire.checkNotNull(existUsername, GlobalErrorCode.USER_NAME_IS_EXIST); }}Copy the code

This pre-validation, of course, can also be encapsulated with a simple extraction UsernameValidate.

But you can’t avoid manually calling validate.

@ Valid optimization

I was wondering if I could solve this manual invocation problem with annotations.

When I saw the @notnull verification annotations, I felt interesting, tried to customize them, and finally achieved the following effect.

@Data
public class UserCreateDTO {

    // Add custom validation annotations
    @CustomValid(NameRepeatValidator.class)
    @notnull (message = "name cannot be empty ")
    @APIModelProperty (value = "name ", Required = true)
    privateString name; . Omit other arguments}@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class NameRepeatValidator implements IValidator<String> {

    private final UserRepository userRepository;

    @Override
    public void valid(String arg) { User existUser = userRepository.findByName(arg); Fire.checkNotNull(existUser, GlobalErrorCode.USER_NAME_IS_EXIST); }}public void create(UserCreateDTO dto) {
  	
    / / remove
    // User existUser = userRepository.findByName(dto.getName());
    // Fire.checkNotNull(existUser, GlobalErrorCode.USER_NAME_IS_EXIST);
  
    // Focus on business logic
    User user = new User();
    user.setPassword(PasswordEncoder.encode(dto.getPassword()));
    user.setName(dto.getName());
    user.setEmail(dto.getEmail());
    userRepository.save(user);
}
Copy the code

To verify the existence of name, add @CustomValid to the NameRepeatValidator class.

The NameRepeatValidator interface is the valid method of the NameRepeatValidator class

Don’t forget to add @ “Validated” to the interface parameters to enable verification.

The principle of

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD})
// VVV specifies the corresponding validator
@Constraint(validatedBy = CustomValidator.class)
public @interface CustomValid {

    // Add additional parametersClass<? extends IValidator<? >> value();String message(a) default "";  // Yes by defaultClass<? >[] groups()default {};  // Yes by default

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

@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class CustomValidator implements ConstraintValidator<CustomValid.Object> {

    private CustomValid customValid;

    private final CustomValidatorStore customValidatorStore;

    @Override
    public void initialize(CustomValid constraintAnnotation) {
        this.customValid = constraintAnnotation;
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if(customValid ! =null) {
            customValidatorStore.valid(value, customValid.value());
        }
        return true; }}Copy the code

Implement a CustomValidator and customize @valid validation.

@customValid annotation implementation, refer to @notnull implementation

  • Specify the CustomValidator as the validator

  • Add one more value parameter, which can be used to obtain a customized pre-validator

The CustomValidator validator implements the ConstraintValidator interface, specifies custom annotations, and validates parameter types.

The implementation class also needs to implement the Initialize and isValid methods

  • Initialize: the method name indicates that it is being initialized
  • IsValid: the parameter verification logic is used. CustomValidatorStore is implemented as follows:

More customizations @valid usage, I won’t expand the details here, you can go out to the right to see other big guy articles.

@Component
public class CustomValidatorStore {

    private finalMap<Class<? >, IValidator<? >> validatorMap;@Autowired
    public CustomValidatorStore(List
       
        > validators)
       > {
        validatorMap = new HashMap<>();
        validators.forEach(validator -> validatorMap.put(validator.getClass(), validator));
    }

    @SuppressWarnings("unchecked")
    public <V> void valid(Object object, Class
        clazz) { IValidator<V> validator = (IValidator<V>) validatorMap.get(clazz); validator.valid((V) object); }}Copy the code

CustomValidatorStore manages the pre-validator. It injects the corresponding IValidator implementation Class into the Spring constructor and converts it into the corresponding Map according to the Class for subsequent valid processing.

conclusion

At this point, the input parameter can be happily verified with an @customValid so that the Service layer can focus on the business logic and not the input parameter.

But this scenario is relatively simple, and it introduces new problems:

  • If two parameters are checked simultaneously.

  • If the value of the existUser parameter is returned, the following code is used:

User existUser = userRepository.findByName(arg); // The existUser parameter may be required for subsequent business logic.Copy the code

Dig a pit here, then come up with a good method to share, or have an idea of a small partner can share your method ~

The last

Above is all content, hope can help you ~

If there is anything wrong, welcome to point out, we exchange and learn together.

Thanks for reading, see you next time.