首页 > 解决方案 > 设计复杂的验证框架:从工厂中找到正确的验证器

问题描述

我正在设计一个验证框架,它将根据传递给我的 Validator 类的对象处理许多不同类型的验证。下面是基于工厂模式的相同实现。

验证器.java

import java.io.Serializable;
import java.util.Map;

/**
 * Validator interface
 *
 * @param <T> generic type
 * @param <M> generic type
 *
 */
@FunctionalInterface
public interface Validator<T, M extends Serializable> {
    /**
     * Validates target object
     *
     * @param object target object
     * @return map of errors (empty if valid)
     */
    Map<String, M> validate(T object);

    /**
     * Validates target object and throws exception in case
     * if list of errors is not empty
     *
     * @param object target object
     * @throws ValidationException if any validation errors exist
     */
    default void validateAndThrow(T object) throws ValidationException {
        Map<String, M> errors = validate(object);
        if (!errors.isEmpty()) {
            throw new ValidationException(errors);
        }
    }

    /**
     * Allows to configure validator if necessary
     *
     * @param visitor Validator visitor
     */
    default void configure(ValidatorVisitor visitor) {
        visitor.visit(this);
    }

    /**
     * Validator visitor functional interface
     */
    @FunctionalInterface
    interface ValidatorVisitor {

        /**
         * Action to be performed on a validator
         *
         * @param validator target validator
         */
        void visit(Validator validator);
    }
}

ValidatorFactory.java

package com.rbs.fsap.aap.apricot.validator;

import com.rbs.fsap.aap.apricot.exception.GenericRuntimeException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

import static com.rbs.fsap.aap.apricot.constants.MessageSource.ERROR_TYPE_VALIDATOR_NOT_FOUND;
import static java.util.Optional.ofNullable;

/**
 * Implementation of validation factory
 *
 * Created by Saransh Bansal on 15/05/2020
 */
@Component
public class ValidatorFactory {

    @Autowired
    List<Validator> validators;

    /**
     * Returns specific validator based on object's class.
     *
     * @param object target object
     * @return instance of {@link Validator}
     */
    public Validator getValidatorForObject(Object object) {
        return validators.stream()
                .filter(v -> {
                    System.out.println("....." + v.getClass());
                    // return v.getClass().isAssignableFrom(object.getClass()); - ???
                })
                .findFirst()
                .orElseThrow(() -> new GenericRuntimeException(ERROR_TYPE_VALIDATOR_NOT_FOUND.getText(
                        object == null ? "null" : object.getClass())));
    }
}

自定义验证器 - DocumentValidator.java

import com.rbs.fsap.aap.apricot.web.dto.DocumentDto;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.rbs.fsap.aap.apricot.constants.GeneralConstants.FIELD_DOCUMENT_FILE;
import static com.rbs.fsap.aap.apricot.constants.MessageSource.*;
import static com.rbs.fsap.aap.apricot.util.MessageBuilder.buildMessage;
import static java.util.Objects.isNull;
import static org.apache.commons.lang.Validate.notNull;
import static org.springframework.util.CollectionUtils.isEmpty;

/**
 * Document upload Request validator
 *
 */
@Component
public class DocumentValidator implements Validator<DocumentDto, Serializable> {
    private static final long DEFAULT_MAX_FILE_SIZE = 5 * 1024L * 1024;

    @Value("${aap.apricot.document.allowedContentTypes:}#{T(java.util.Collections).emptyList()}")
    private List<String> allowedContentTypes;

    @Value("${aap.apricot.document.allowedFileNames:}#{T(java.util.Collections).emptyList()}")
    private List<String> allowedFileNames;

    @Value("${aap.apricot.document.max.size:" + DEFAULT_MAX_FILE_SIZE + "}")
    private long maxFileSize;

    @Override
    public Map<String, Serializable> validate(DocumentDto documentData) {
        notNull(documentData, ERROR_MSG_DOCUMENT_MISSED.getText());

        Map<String, Serializable> errors = new HashMap<>();

        if (isNull(documentData.getFile())) {
            errors.put(FIELD_DOCUMENT_FILE.getText(), ERROR_MSG_DOCUMENT_FILE_MISSED.getText());
        } else {
            String contentType = documentData.getFile().getContentType();
            String fileName = documentData.getFile().getOriginalFilename();

            if (isNull(documentData.getBusinessDate())) {
                errors.put(FIELD_DOCUMENT_FILE.getText(), buildMessage(ERROR_MSG_DOCUMENT_BUSINESS_DATE_MISSED.getText()));
            }
            if (documentData.getFile().getSize() > maxFileSize) {
                errors.put(FIELD_DOCUMENT_FILE.getText(), buildMessage(ERROR_MSG_DOCUMENT_FILE_SIZE_EXCEEDED.getText(), maxFileSize));
            }
            if (!isEmpty(allowedContentTypes) && contentType != null &&
                    allowedContentTypes.stream().noneMatch(contentType::equalsIgnoreCase)) {
                errors.put(FIELD_DOCUMENT_FILE.getText(), buildMessage(ERROR_MSG_DOCUMENT_RESTRICTED_CONTENT_TYPE.getText(), contentType));
            }
            if (!isEmpty(allowedFileNames) && fileName != null &&
                        allowedFileNames.stream().noneMatch(fileName::startsWith)) {
                errors.put(FIELD_DOCUMENT_FILE.getText(), buildMessage(ERROR_MSG_DOCUMENT_RESTRICTED_FILE_NAME.getText(), fileName));
            }
        }
        return errors;
    }
}

我似乎无法弄清楚如何从我的 Factory 类中正确返回验证器。(参见???ValidatorFactory.java 中注释的代码)

任何人都可以提供同样的帮助。

标签: javaspring-bootfactory-pattern

解决方案


根据我的理解,您的工厂需要根据特定类型返回。

粗略的代码给你一个想法:

ValidatorFactory 将从其 Validator 列表中过滤,基于它们是否支持给定的对象。

请记住,如果 >1 验证器支持给定类型,那么您的过滤器将返回 2 或更多。您只会返回 findFirst()。

选项1:

Public class ValidatorFactory {

    validators.stream().filter(v -> v.supports(object.getClass()).findFirst();


}

public class DocumentValidator{

  public boolean supports(Class clazz){

   return true;
   }
}

选项 2:

public class ValidatorFactory{
  
   private Map<Class, Validator> validatorAssociations = new HashMap<>();


   public Validator getValidator(Object object){
 
      return validatorAssociations.get(object.class);
   }
  
}

您将需要某种映射,无论是 ValidatorFactory 的职责(FactoryPattern 通常就是这种情况),还是您将职责下推给验证器以了解其自身的能力。由你决定。


推荐阅读