累积验证违规的设计模式

时间:2014-05-13 07:42:31

标签: java validation design-patterns exception-handling

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

{"date":"2014-05-05", "url":"http://some.website.com","counter":3}
  • 此数据应经过验证正式date的值应为a 可解析的日期,url也应符合正常的网址语法。
  • 此外,此数据应验证逻辑date将来应该是url应该是可访问的 地址,返回200 OK

为了使它干净,必须将这两个验证例程分成不同的单元(类,工具,等等)。但是,所需的最终行为必须让用户清楚地了解数据中存在的所有违规行为。类似的东西:

{"Errors":[ "Specified date is not in the future",//Formal validation failed "Specified URL has invalid syntax"//Logical validation failed ]}

  • 我已经看到了所需行为的一些实现,但它们 使用那些使用Error个对象,并充满了像 Error.hasErrors()error==null,看起来并不优雅。
  • 我还看到javax.validation的实施,它会立即为您提供所有违规行为。可以为内容验证实现相同的方法,但我不确定,这是执行此操作的最佳方法。

问题:处理多种异常/违规行为的最佳做法是什么?

UPD:答案的简短摘要:收集Violations,构建Exception,包含其上下文,原因和说明,使用拦截器进行渲染。请参阅答案中的参考链接:

http://beanvalidation.org/1.0/spec/ JSR 303规范

http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/validation.html Spring Bean验证

http://docs.oracle.com/javaee/6/tutorial/doc/gircz.html Java EE验证

Which Design Pattern To Use For Validation

Why not use exceptions as regular flow of control?

5 个答案:

答案 0 :(得分:3)

我认为没有最佳做法,因为这取决于您尝试实现的目标。在我看来,不应将异常及其消息直接显示给用户。例外对于技术而言非常重要,并且在很大程度上取决于它们被抛出的背景。因此,我的方法是设计一个容器类型,它收集验证引发的所有异常。这些异常应尽可能多地保留上下文(不是以异常消息的形式,而是以传递给构造函数的字段的形式)。提供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);
    }
}

答案 1 :(得分:3)

您可以执行以下操作:

定义一个抽象的Check类,如下所示:

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);
}

class Data是保存数据的类(需要检查)。可以是String,JSON对象,有什么。

Error表示在数据中检测到的问题大致应该是:

public class Error {
  private String problem;
  public Error(String problem) { this.problem = problem }
  public String getProblem() { return problem }
  // maybe additional fields and method to better describe the detected problem...
}

然后,您可以使用代码对数据进行检查:

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);
     }
   }
}

略微改变了我的原始答案。在当前的答案中,存在子检查的概念:如果名为x的检查具有名为y的子检查,则y检查仅在x检查成功时运行。例如,如果Date不可解析,那么将来检查它是没有意义的。

在你的情况下,我认为所有/大多数逻辑检查应该是正式检查的子检查。

答案 2 :(得分:1)

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

你可以(我希望你这样做)使用经过验证的框架,可以很好地处理它。我通过Spring谈到JSR 303和Bean Validation:http://docs.spring.io/spring/docs/3.2.x/spring-framework-reference/html/validation.html

需要一段时间才能习惯,但它会让你回报1000倍。

答案 3 :(得分:1)

我之前已经回答了Here

标记为良好的答案是应用于验证的复合模式的示例(几乎)

当然,有很多框架可供选择。你可以做的聪明,我已经习惯了很好的效果,就是使用方面+验证器,或确保自动魔法检查整个新的和现有的代码。

@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);
}
}

这是一个bean配置的剪辑

<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>

现在,您的所有控制器方法都将在此处和将来应用验证代码。

答案 4 :(得分:0)

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