让我们想象一下,我们有一个流程,它接受以下类型的数据:
{"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验证
答案 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 ...处理路径上的每个方法都可以附加到列表中。