掃二維碼與項(xiàng)目經(jīng)理溝通
我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
項(xiàng)目中參數(shù)校驗(yàn)十分重要,它可以保護(hù)我們應(yīng)用程序的安全性和合法性。我想大家通常的做法是像下面這樣做的:

網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)!專注于網(wǎng)頁設(shè)計、網(wǎng)站建設(shè)、微信開發(fā)、小程序制作、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了城中免費(fèi)建站歡迎大家使用!
@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
}
這么做最大的優(yōu)勢就是簡單直接,但是如果驗(yàn)證邏輯很復(fù)雜,會導(dǎo)致這個類變得很龐大,而且上面是通過拋出異常來改變代碼執(zhí)行流程,這也是一種不推薦的做法。
那么有什么更好的參數(shù)校驗(yàn)的方式呢?本文就推薦一種通過責(zé)任鏈設(shè)計模式來優(yōu)雅地實(shí)現(xiàn)參數(shù)的校驗(yàn)功能,我們通過一個用戶注冊的例子來講明白如何實(shí)現(xiàn)。
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)
private final String email;
@NotBlank
@Size(min = 6, max = 20)
private final String rawPassword;
@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;
}
}
public abstract class ValidationStep{
private ValidationStepnext;
public ValidationSteplinkWith(ValidationStep next) {
if (this.next == null) {
this.next = next;
return this;
}
ValidationSteplastStep = 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);
}
}
現(xiàn)在我們開始進(jìn)行參數(shù)校驗(yàn)的核心邏輯,也就是如何把上面定義的類給串聯(lián)起來。
public interface SignUpValidationService {
ValidationResult validate(SignUpCommand command);
}
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{
@Override
public ValidationResult validate(SignUpCommand command) {
try (ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory()) {
final Validator validator = validatorFactory.getValidator();
final Set> constraintsViolations = validator.validate(command);
if (!constraintsViolations.isEmpty()) {
return ValidationResult.invalid(constraintsViolations.iterator().next().getMessage());
}
}
return checkNext(command);
}
}
@AllArgsConstructor
private static class UsernameDuplicationValidationStep extends ValidationStep{
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{
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);
}
}
}
上面就是通過責(zé)任鏈模式來實(shí)現(xiàn)我們參數(shù)校驗(yàn)的完整過程了,你學(xué)會了嗎?這種方式可以優(yōu)雅的將驗(yàn)證邏輯拆分到單獨(dú)的類中,如果添加新的驗(yàn)證邏輯,只需要添加新的類,然后組裝到“校驗(yàn)鏈”中。但是在我看來,這比較適合于用于校驗(yàn)相對復(fù)雜的場景,如果只是簡單的校驗(yàn)就完全沒必要這么做了,反而會增加代碼的復(fù)雜度。

我們在微信上24小時期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流