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
- Defina uma
SignUpCommand
classe para aceitar as informações de atributo cadastradas pelo usuário. E use@Value
anotaçõ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.validation
anotações como@NotBlank
para@Size
verificar se as informações de registro do usuário são válidas. lombok
Anotação usada porque@Value
desejo 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.
- Defina uma classe que armazene os resultados da validação
ValidationResult
da 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.
- 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.
- Definimos uma classe de interface para verificação de registro
SignUpValidationService
public interface SignUpValidationService {
ValidationResult validate(SignUpCommand command);
}
复制代码
- 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);
}
}
}
复制代码
validate
O método é o método principal, que chamalinkWith
o 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 é repetidoEmail
.CommandConstraintsValidationStep
Turma, essa etapa é uma verificação básica, tudojavax validation annotation
será verificado, como se está vazio,Email
se o formato está correto, etc. Isso é muito conveniente, não precisamos escrever esses validadores nós mesmos. Se um objeto for válido, chamarcheckNext
o método permite que o processo vá para a próxima etapa,checkNext
caso contrário,ValidationResult
ele retornará imediatamente.UsernameDuplicationValidationStep
Classe, esta etapa verifica se o nome do usuário é repetido, principalmente precisa verificar o banco de dados. Se for, ele retornará inválido imediatamenteValidationResult
, caso contrário, continue voltando e verifique a próxima etapa.EmailDuplicationValidationStep
classe, verificação de repetição de e-mail. Como não há próxima etapa, se o e-mail for único, ele será retornadoValidationResult.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]