Java 累积验证冲突的设计模式

Java 累积验证冲突的设计模式,java,validation,design-patterns,exception-handling,Java,Validation,Design Patterns,Exception Handling,让我们想象一下,我们有一个进程,它接受以下类型的数据: {"date":"2014-05-05", "url":"http://some.website.com","counter":3} 应正式验证该数据:日期的值应为 可解析日期,url也应符合正常的url语法 此外,应在逻辑上验证此数据:日期应在将来,url应是可访问的 地址,返回200 OK 为了使它干净,必须将这两个验证例程分离为不同的单元(类、util等等)。然而,期望的最终行为必须让用户清楚地了解数据中存在的所有违规行为。比如

让我们想象一下,我们有一个进程,它接受以下类型的数据:

{"date":"2014-05-05", "url":"http://some.website.com","counter":3}
  • 应正式验证该数据:
    日期的值应为
    可解析日期,
    url
    也应符合正常的url语法
  • 此外,应在逻辑上验证此数据:
    日期应在将来,
    url
    应是可访问的 地址,返回
    200 OK
为了使它干净,必须将这两个验证例程分离为不同的单元(类、util等等)。然而,期望的最终行为必须让用户清楚地了解数据中存在的所有违规行为。比如:

{“错误”:[
“指定的日期不是将来”,//正式验证失败
“指定的URL具有无效语法”//逻辑验证失败
]}

  • 我已经看到了一些所需行为的实现,但是它们 使用那些利用
    Error
    对象并充满检查的对象,如
    Error.hasErrors()
    Error==null
    ,这看起来并不优雅
  • 我还看到了
    javax.validation
    的实现,它一次给出了所有字段上的所有违规行为。同样的方法也可以用于内容验证,但我不确定这是否是最好的方法
问题:处理多个不同性质的异常/违规的最佳实践是什么

UPD:答案摘要:收集
违规行为
,构建
异常
,包含其上下文、原因和描述,使用拦截器呈现。请参阅答案中的参考链接:

JSR303规范

SpringBean验证

JavaEE验证

我认为没有最佳实践,因为这取决于你努力实现的目标。在我看来,不应使用异常及其消息直接向用户显示。异常在很大程度上是技术性的,并且在很大程度上取决于抛出异常的上下文。因此,我的方法是设计一个容器类型,它收集验证引发的所有异常。这些异常应该尽可能多地保留上下文(不是以异常消息的形式,而是以传递给构造函数的字段的形式)。提供getter方法以使这些字段(属性)可访问。在呈现视图时,您可以迭代容器的所有条目,并生成一条适当的、人类可读的i18n消息

下面是@Alexandre Santos注释所要求的一些伪代码。它既不精致也不专业,只是我的初稿。因此,不要期待优秀的设计。这只是一个如何实施/设计的示例:

public static void main(String[] args) {
    Violations violations = new Violations();
    Integer age = AgeValidator.parse("0042", "age", violations);
    URL url = UrlValidator.parse("http://some.website.com", "url", violations);
}

// Validator defining all the rules for a valid age value
public class AgeValidator {

    // Collection of validation rules for age values
    private static final Collection<Validator<String>> VALIDATORS = ...;

    // Pass in the value to validate, the name of the field
    // defining the value and the container to collect all
    // violations (could be a Map<String, ValidationException>)
    //
    // a return value of null indicates at least one rule violation
    public static Integer parse(String value, String name, Violations violations) {
        try {
            for (Validator<String> validator : VALIDATORS) validator.validate(value);
        } catch (ValidationException e) {
            violations.add(name, e);
        }
        return violations.existFor(name) ? null : Integer.parseInt(value);
    }
}
publicstaticvoidmain(字符串[]args){
违规行为=新的违规行为();
整数age=AgeValidator.parse(“0042”,“age”,违规);
URL=UrlValidator.parse(“http://some.website.com“,”url“,违规行为);
}
//为有效年龄值定义所有规则的验证器
公共类验证程序{
//年龄值的验证规则集合
私有静态最终集合验证器=。。。;
//传入要验证的值,即字段的名称
//定义要收集所有数据的值和容器
//违规行为(可能是地图)
//
//返回值为null表示至少有一个规则冲突
公共静态整数解析(字符串值、字符串名称、冲突){
试一试{
for(Validator-Validator:VALIDATORS)Validator.validate(value);
}捕获(ValidationException e){
增加(姓名,e);
}
返回冲突.existFor(名称)?null:Integer.parseInt(值);
}
}

使用异常进行设计是可行的,但您必须编写一个完整的框架来处理异常,其中许多异常是异常拦截器无法处理的。如果你觉得编码很痒,那就去做吧。我的建议是有不同类别的例外情况。其中一些是关键例外,一些只是警告。。。你明白了

您可以(我希望您可以)使用一个经过验证的框架来完美地处理这个问题。我谈到JSR303和Spring中的Bean验证:


这需要一段时间来适应,但它会给你1000倍的回报。

我会简单地把所有错误的列表传递给你。列表中的项目可能不仅仅是例外,而是一些包含有关错误的更多信息的对象,例如错误参数的名称、错误值、错误在字符串中的位置、验证类型(正式、法律)、错误消息的ID,以便本地化显示给用户。。。处理路径上的每个方法都可以附加到列表中。

您可以执行以下操作:

定义抽象检查类,如下所示:

public abstract class Check {
  private final List<Check> subChecks = new ArrayList<Check>();

  public Check add(Check subCheck) { subChecks.add(subCheck); return this }

  public void run(Data dataToInspect, List<Error> errors) {
    Error e = check(dataToInspect);
    if (e != null) {
       errors.add(e);
       return;
    }
    for (Check subCheck : subChecks) {
      subCheck.run(dataToInspect, errors);
    }
  }

  // Returns null if dataToInspect is OK.
  public abstract Error check(Data dataToInspect);
}
然后,您就有了针对一段数据运行检查的代码:

public class Checker {

   private final List<Error> errors = new ArrayList<Error>();
   private final List<Check> checks = new ArrayList<Check>();

   public Checker() {
     checks.add(new DateIsParsableCheck().add(new DateIsInTheFurutreCheck());
     checks.add(new UrlIsWellFormed().add(new UrlIsAccessible());

     checks.add();
     ..
   }

   public void check(Data d) {
     for (Check c : checks) {
       Error e = c.run(d, errors);
       if (e != null) 
         errors.add(e);
     }
   }
}
公共类检查器{
私有最终列表错误=新建ArrayList();
私有最终列表检查=新建ArrayList();
公共检查器(){
checks.add(new DateIsParsableCheck().add(new DateIsInTheFurutreCheck());
检查.add(新UrlIsWellFormed().add(新UrlIsAccessible());
检查。添加();
..
}
公共作废检查(数据d){
用于(检查c:检查){
错误e=c.run(d,错误);
如果(e!=null)
添加(e);
}
}
}
稍微改变了我原来的答案。在当前的答案中有子检查的概念:如果一个名为
x
的检查有一个名为
y
的子检查,那么
y
检查只有在
x
检查成功时才会运行。例如,如果日期不可解析,则没有必要在将来检查它

在你的情况下,我认为所有/大多数逻辑检查都应该是s
public class Checker {

   private final List<Error> errors = new ArrayList<Error>();
   private final List<Check> checks = new ArrayList<Check>();

   public Checker() {
     checks.add(new DateIsParsableCheck().add(new DateIsInTheFurutreCheck());
     checks.add(new UrlIsWellFormed().add(new UrlIsAccessible());

     checks.add();
     ..
   }

   public void check(Data d) {
     for (Check c : checks) {
       Error e = c.run(d, errors);
       if (e != null) 
         errors.add(e);
     }
   }
}
@Aspect
public class DtoValidator {

private Validator validator;

public DtoValidator() {
}

public DtoValidator(Validator validator) {
    this.validator = validator;
}

public void doValidation(JoinPoint jp){
    for( Object arg : jp.getArgs() ){
        if (arg != null) {
            Set<ConstraintViolation<Object>> violations = validator.validate(arg);
            if( violations.size() > 0 ){
                throw buildError(violations);
            }
        }
    }
}

private static BadRequestException buildError( Set<ConstraintViolation<Object>> violations ){
    Map<String, String> errorMap = new HashMap<String, String>();
    for( ConstraintViolation error : violations ){
        errorMap.put(error.getPropertyPath().toString(), error.getMessage());
    }
    return new BadRequestException(errorMap);
}
}
<aop:config proxy-target-class="true">
    <aop:aspect id="dtoValidator" ref="dtoValidator" order="10">
        <aop:before method="doValidation"
                    pointcut="execution(public * com.mycompany.ws.controllers.bs.*.*(..))"/>
    </aop:aspect>
</aop:config>