¿Cómo realizar elegantemente la validación de parámetros de forma predeterminada utilizando la cadena de responsabilidad?

Este artículo está participando en el "Proyecto Piedra Dorada"

prefacio

La verificación de parámetros en el proyecto es muy importante, puede proteger la seguridad y la legalidad de nuestra aplicación. Creo que todo el mundo suele hacer algo como esto:

@Override
public void validate(SignUpCommand command) {
    validateCommand(command); // will throw an exception if command is not valid
    validateUsername(command.getUsername()); // will throw an exception if username is duplicated
    validateEmail(commend.getEmail()); // will throw an exception if email is duplicated
}
复制代码

La mayor ventaja de hacer esto es que es simple y directo, pero si la lógica de verificación es compleja, la clase se volverá muy grande, y lo anterior es cambiar el proceso de ejecución del código lanzando excepciones, lo cual tampoco es recomendable.

Entonces, ¿qué mejor manera de verificar los parámetros? Este artículo recomienda una forma elegante de implementar la función de verificación de parámetros a través del patrón de diseño de la cadena de responsabilidad. Usamos un ejemplo de registro de usuario para entender cómo implementarlo.

  • Datos de registro válidos: nombre, apellido, correo electrónico, nombre de usuario y contraseña.
  • El nombre de usuario debe ser único.
  • El correo electrónico debe ser único.

Definir clases de resultados de registro y autenticación de usuarios

  1. Defina una SignUpCommandclase para aceptar la información de atributo registrada por el usuario. Y use @Valueanotaciones para hacer que esta clase sea inmutable.
import lombok.Value;

import javax.validation.constraints.*;

@Value
public class SignUpCommand {

    @Min(2)
    @Max(40)
    @NotBlank
    private final String firstName;

    @Min(2)
    @Max(40)
    @NotBlank
    private final String lastName;

    @Min(2)
    @Max(40)
    @NotBlank
    private final String username;

    @NotBlank
    @Size(max = 60)
    @Email
    private final String email;

    @NotBlank
    @Size(min = 6, max = 20)
    private final String rawPassword;
复制代码
  • Use javax.validationanotaciones como @NotBlankpara @Sizeverificar si la información de registro del usuario es válida.
  • lombokAnotación utilizada porque @Valuequiero que el objeto de comando sea inmutable. Los datos del usuario registrado deben ser los mismos que los rellenados en el formulario de registro.
  1. Defina una clase que almacene los resultados de validación ValidationResultde la siguiente manera:
@Value
public class ValidationResult {
    private final boolean isValid;
    private final String errorMsg;

    public static ValidationResult valid() {
        return new ValidationResult(true, null);
    }

    public static ValidationResult invalid(String errorMsg) {
        return new ValidationResult(false, errorMsg);
    }

    public boolean notValid() {
        return !isValid;
    }
}
复制代码
  • En mi opinión, este es un tipo de retorno de método muy conveniente y mejor que lanzar una excepción con un mensaje de validación.
  1. Dado que es una cadena de responsabilidad, también necesitamos definir una clase "cadena" ValidationStep, que es la superclase de estos pasos de verificación, y queremos "vincularlos" entre sí.
public abstract class ValidationStep<T> {

    private ValidationStep<T> next;

    public ValidationStep<T> linkWith(ValidationStep<T> next) {
        if (this.next == null) {
            this.next = next;
            return this;
        }
        ValidationStep<T> lastStep = this.next;
        while (lastStep.next != null) {
            lastStep = lastStep.next;
        }
        lastStep.next = next;
        return this;
    }

    public abstract ValidationResult validate(T toValidate);

    protected ValidationResult checkNext(T toValidate) {
        if (next == null) {
            return ValidationResult.valid();
        }

        return next.validate(toValidate);
    }
}
复制代码

Lógica de validación central

Ahora comenzamos la lógica central de la verificación de parámetros, es decir, cómo conectar las clases definidas anteriormente en serie.

  1. Definimos una clase de interfaz para la verificación de registroSignUpValidationService
public interface SignUpValidationService {
    ValidationResult validate(SignUpCommand command);
}
复制代码
  1. Ahora podemos usar las clases definidas anteriormente y el patrón Cadena de responsabilidad para implementar fácilmente, el código es el siguiente:
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.util.Set;

@Service
@AllArgsConstructor
public class DefaultSignUpValidationService implements SignUpValidationService {

    private final UserRepository userRepository;

    @Override
    public ValidationResult validate(SignUpCommand command) {
        return new CommandConstraintsValidationStep()
                .linkWith(new UsernameDuplicationValidationStep(userRepository))
                .linkWith(new EmailDuplicationValidationStep(userRepository))
                .validate(command);
    }

    private static class CommandConstraintsValidationStep extends ValidationStep<SignUpCommand> {

        @Override
        public ValidationResult validate(SignUpCommand command) {
            try (ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory()) {
                final Validator validator = validatorFactory.getValidator();
                final Set<ConstraintViolation<SignUpCommand>> constraintsViolations = validator.validate(command);

                if (!constraintsViolations.isEmpty()) {
                    return ValidationResult.invalid(constraintsViolations.iterator().next().getMessage());
                }
            }
            return checkNext(command);
        }
    }

    @AllArgsConstructor
    private static class UsernameDuplicationValidationStep extends ValidationStep<SignUpCommand> {

        private final UserRepository userRepository;

        @Override
        public ValidationResult validate(SignUpCommand command) {
            if (userRepository.findByUsername(command.getUsername()).isPresent()) {
                return ValidationResult.invalid(String.format("Username [%s] is already taken", command.getUsername()));
            }
            return checkNext(command);
        }
    }

    @AllArgsConstructor
    private static class EmailDuplicationValidationStep extends ValidationStep<SignUpCommand> {

        private final UserRepository userRepository;

        @Override
        public ValidationResult validate(SignUpCommand command) {
            if (userRepository.findByEmail(command.getEmail()).isPresent()) {
                return ValidationResult.invalid(String.format("Email [%s] is already taken", command.getEmail()));
            }
            return checkNext(command);
        }
    }
}
复制代码
  • validateEl método es el método central, que llama linkWithal validador de cadena de los parámetros de ensamblaje del método, lo que involucra múltiples clases de verificación, primero haga la verificación básica, si se pasa, para verificar si el nombre de usuario se repite, si también se pasa, para verificar si Emailse repite
  • CommandConstraintsValidationStepClase, este paso es una verificación básica, javax validation annotationse verificará todo, como si está vacío, Emailsi el formato es correcto, etc. Esto es muy conveniente, no tenemos que escribir estos validadores nosotros mismos. Si un objeto es válido, llamar checkNextal método permite que el proceso vaya al siguiente paso; checkNextsi no, ValidationResultregresará inmediatamente.
  • UsernameDuplicationValidationStepClase, este paso verifica si el nombre de usuario se repite, principalmente necesita verificar la base de datos. Si es así, volverá inválido inmediatamente ValidationResult; de ​​lo contrario, continúe retrocediendo y verifique el siguiente paso.
  • EmailDuplicationValidationStepclase, verificación de repetición de correo electrónico. Dado que no hay un paso siguiente, si el correo electrónico es único, se devolverá ValidationResult.valid().

Resumir

Lo anterior es el proceso completo de realización de nuestra verificación de parámetros a través del modo de cadena de responsabilidad. ¿Lo ha aprendido? Este método puede dividir elegantemente la lógica de verificación en una clase separada. Si agrega una nueva lógica de verificación, solo necesita agregar una nueva clase, y luego se ensamblan en la "cadena de verificación". Pero en mi opinión, esto es más adecuado para escenarios relativamente complejos.Si es solo una simple verificación, no hay necesidad de hacer esto en absoluto, pero aumentará la complejidad del código.

Bienvenido a prestar atención al intercambio y estudio de la cuenta pública personal [JAVA Xuyang]

Supongo que te gusta

Origin juejin.im/post/7215386649676791867
Recomendado
Clasificación