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
- Defina una
SignUpCommand
clase para aceptar la información de atributo registrada por el usuario. Y use@Value
anotaciones 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.validation
anotaciones como@NotBlank
para@Size
verificar si la información de registro del usuario es válida. lombok
Anotación utilizada porque@Value
quiero que el objeto de comando sea inmutable. Los datos del usuario registrado deben ser los mismos que los rellenados en el formulario de registro.
- Defina una clase que almacene los resultados de validación
ValidationResult
de 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.
- 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.
- Definimos una clase de interfaz para la verificación de registro
SignUpValidationService
public interface SignUpValidationService {
ValidationResult validate(SignUpCommand command);
}
复制代码
- 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);
}
}
}
复制代码
validate
El método es el método central, que llamalinkWith
al 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 siEmail
se repiteCommandConstraintsValidationStep
Clase, este paso es una verificación básica,javax validation annotation
se verificará todo, como si está vacío,Email
si el formato es correcto, etc. Esto es muy conveniente, no tenemos que escribir estos validadores nosotros mismos. Si un objeto es válido, llamarcheckNext
al método permite que el proceso vaya al siguiente paso;checkNext
si no,ValidationResult
regresará inmediatamente.UsernameDuplicationValidationStep
Clase, este paso verifica si el nombre de usuario se repite, principalmente necesita verificar la base de datos. Si es así, volverá inválido inmediatamenteValidationResult
; de lo contrario, continúe retrocediendo y verifique el siguiente paso.EmailDuplicationValidationStep
clase, 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]