Como executar elegantemente a validação de parâmetros por padrão usando a cadeia de responsabilidade?

Este artigo está participando do "Projeto Pedra Dourada"

prefácio

A verificação de parâmetros no projeto é muito importante, pois pode proteger a segurança e a legalidade de nosso aplicativo. Acho que todo mundo costuma fazer algo assim:

@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
}
复制代码

A maior vantagem de fazer isso é que é simples e direto, mas se a lógica de verificação for complexa, a classe ficará muito grande, e o acima é alterar o processo de execução do código lançando exceções, o que também não é recomendado.

Então, que melhor maneira de verificar os parâmetros? Este artigo recomenda uma maneira elegante de implementar a função de verificação de parâmetros por meio do padrão de design chain of Responsibility. Usamos um exemplo de registro de usuário para entender como implementá-lo.

  • Dados cadastrais válidos - nome, sobrenome, e-mail, nome de usuário e senha.
  • O nome de usuário deve ser único.
  • O e-mail deve ser único.

Definir classes de resultado de registro e autenticação do usuário

  1. Defina uma SignUpCommandclasse para aceitar as informações de atributo cadastradas pelo usuário. E use @Valueanotações para tornar essa classe imutável.
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.validationanotações como @NotBlankpara @Sizeverificar se as informações de registro do usuário são válidas.
  • lombokAnotação usada porque @Valuedesejo que o objeto de comando seja imutável. Os dados do utilizador registado deverão ser iguais aos dados preenchidos no formulário de registo.
  1. Defina uma classe que armazene os resultados da validação ValidationResultda seguinte maneira:
@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;
    }
}
复制代码
  • Na minha opinião, este é um tipo de retorno de método muito conveniente e melhor do que lançar uma exceção com uma mensagem de validação.
  1. Como é uma cadeia de responsabilidade, também precisamos definir uma classe "cadeia" ValidationStep, que é a superclasse dessas etapas de verificação, e queremos "vinculá-las" umas às outras.
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 validação principal

Agora iniciamos a lógica central da verificação de parâmetros, ou seja, como conectar as classes definidas acima em série.

  1. Definimos uma classe de interface para verificação de registroSignUpValidationService
public interface SignUpValidationService {
    ValidationResult validate(SignUpCommand command);
}
复制代码
  1. Agora podemos usar as classes definidas acima e o padrão Chain of Responsibility para implementar facilmente, o código é o seguinte:
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);
        }
    }
}
复制代码
  • validateO método é o método principal, que chama linkWitho validador de cadeia dos parâmetros de montagem do método, que envolve várias classes de verificação, primeiro faça a verificação básica, se passado, para verificar se o nome do usuário é repetido, se também passado, para verificar se ele é repetido Email.
  • CommandConstraintsValidationStepTurma, essa etapa é uma verificação básica, tudo javax validation annotationserá verificado, como se está vazio, Emailse o formato está correto, etc. Isso é muito conveniente, não precisamos escrever esses validadores nós mesmos. Se um objeto for válido, chamar checkNexto método permite que o processo vá para a próxima etapa, checkNextcaso contrário, ValidationResultele retornará imediatamente.
  • UsernameDuplicationValidationStepClasse, esta etapa verifica se o nome do usuário é repetido, principalmente precisa verificar o banco de dados. Se for, ele retornará inválido imediatamente ValidationResult, caso contrário, continue voltando e verifique a próxima etapa.
  • EmailDuplicationValidationStepclasse, verificação de repetição de e-mail. Como não há próxima etapa, se o e-mail for único, ele será retornado ValidationResult.valid().

Resumir

O processo acima é completo para realizar nossa verificação de parâmetros por meio do modo de cadeia de responsabilidade. Você já aprendeu? Esse método pode dividir elegantemente a lógica de verificação em uma classe separada. Se você adicionar uma nova lógica de verificação, precisará apenas adicionar uma nova classe e, em seguida, montados na "cadeia de verificação". Mas, na minha opinião, isso é mais adequado para cenários relativamente complexos.Se for apenas uma verificação simples, não há necessidade de fazer isso, mas aumentará a complexidade do código.

Bem-vindo a prestar atenção ao intercâmbio e estudo da conta pública pessoal [JAVA Xuyang]

Acho que você gosta

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