设计复杂的验证框架:从工厂中找到正确的验证器
Designing a sophisticated validation framework: Find correct validator from factory
我正在设计一个验证框架,它将根据我传递给验证器 class 的对象处理许多不同类型的验证。下面是基于工厂模式的实现。
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())));
}
}
自定义验证器 - 文档Validator.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;
}
}
我似乎无法弄清楚如何正确地return 来自我的工厂 class 的验证器。 (见ValidatorFactory.java中用???
注释的代码)
谁能帮忙解决一下。
根据我的理解,您的工厂需要 return 基于特定类型。
给你一个想法的粗略代码:
ValidatorFactory 将根据其是否支持给定对象从其验证器列表中进行过滤。
请记住,如果支持给定类型的验证器超过 1 个,那么您的过滤器将 return 2 个或更多。你只会 return 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 通常是这种情况),还是您将责任推给验证器以了解其自身的功能。由你决定。
我在某个项目上工作,我必须创建一种机制来访问工厂中的某些实现,而我事先并不知道有多少实现以及哪些实现。因此,我编写了一个功能,其中每个实现在其自己的构造函数中都会将自己插入到工厂中,并在其 class 名称或自定义提供的名称下注册自己。我称它为自实例化工厂。我将该功能作为我自己的名为 MgntUtils 的开源库的一部分发布,并撰写了一些文章来解释它的编写方式和使用方式。此外,库源代码还包含一个工作示例。它可能对你有用。解决您的问题。这是Javadoc that explains the feature in short. Here is the article about the library (Look for paragraph called Lifecycle management (Self-instantiating factories). Also there is a separate article dedicated to just this particular feature. The MgntUtils library could be obtained as Maven Artifact or on Github(包括Javadoc和源代码)
我正在设计一个验证框架,它将根据我传递给验证器 class 的对象处理许多不同类型的验证。下面是基于工厂模式的实现。
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())));
}
}
自定义验证器 - 文档Validator.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;
}
}
我似乎无法弄清楚如何正确地return 来自我的工厂 class 的验证器。 (见ValidatorFactory.java中用???
注释的代码)
谁能帮忙解决一下。
根据我的理解,您的工厂需要 return 基于特定类型。
给你一个想法的粗略代码:
ValidatorFactory 将根据其是否支持给定对象从其验证器列表中进行过滤。
请记住,如果支持给定类型的验证器超过 1 个,那么您的过滤器将 return 2 个或更多。你只会 return 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 通常是这种情况),还是您将责任推给验证器以了解其自身的功能。由你决定。
我在某个项目上工作,我必须创建一种机制来访问工厂中的某些实现,而我事先并不知道有多少实现以及哪些实现。因此,我编写了一个功能,其中每个实现在其自己的构造函数中都会将自己插入到工厂中,并在其 class 名称或自定义提供的名称下注册自己。我称它为自实例化工厂。我将该功能作为我自己的名为 MgntUtils 的开源库的一部分发布,并撰写了一些文章来解释它的编写方式和使用方式。此外,库源代码还包含一个工作示例。它可能对你有用。解决您的问题。这是Javadoc that explains the feature in short. Here is the article about the library (Look for paragraph called Lifecycle management (Self-instantiating factories). Also there is a separate article dedicated to just this particular feature. The MgntUtils library could be obtained as Maven Artifact or on Github(包括Javadoc和源代码)