为 OVal 铺好了路、为 AOP 过程做了引导之后,可以开始使用 Java 5 标注来为代码指定简单的约束条件了。
OVal 的可重用约束条件
用 OVal 为方法指定前置条件必须对方法参数进行标注。相应地,当调用一个用 OVal 约束条件标注过的方法时,OVal 会在该方法真正执行前 验证该约束条件。
在我的例子中,我想要指定当 Class 参数的值为 null 时,buildHierarchy 方法不能被调用。OVal 通过 @NotNull 标注支持此约束条件,该标注在方法所需的所有参数前指定。也要注意,任何想要使用 OVal 约束条件的类也必须在类层次上指定 @Guarded 标注,像我在清单 7 中所做的那样:
清单 7. OVal 约束条件
import net.sf.oval.annotations.Guarded;
import net.sf.oval.constraints.NotNull;
@Guarded
public class HierarchyBuilder {
public static Hierarchy buildHierarchy(@NotNull Class clzz){
Hierarchy hier = new Hierarchy();
hier.setBaseClass(clzz);
Class superclass = clzz.getSuperclass();
if(superclass != null && superclass.getName().equals("java.lang.Object")){
return hier;
}else{
while((clzz.getSuperclass() != null) &&
(!clzz.getSuperclass().getName().equals("java.lang.Object"))){
clzz = clzz.getSuperclass();
hier.addClass(clzz);
}
return hier;
}
}
}
通过标注指定这个约束条件意味着我的代码不再会被重复的条件弄得乱七八糟,这些条件检查 null 值,并且一旦找到该值会抛出异常。现在这项逻辑由 OVal 处理,且处理的方法有些相似 —— 事实上,如果违反了约束条件,OVal 会抛出一个 ConstraintsViolatedException,它是 RuntimeException 的子类。
当然,我下一步要编译 HierarchyBuilder 类和 清单 5 中相应的 DefaultGuardAspect 类。我用 清单 6 中的 iajc 任务来实现这一目的,这样我能把 OVal 的行为编入我的代码中了。
接下来,我更新 清单 4 中的测试用例来验证是否抛出了一个 ConstraintsViolatedException,如清单 8 所示:
清单 8. 验证是否抛出了 ConstraintsViolatedException
@Test(expectedExceptions={ConstraintsViolatedException.class})
public void verifyHierarchyNull() throws Exception{
Class clzz = null;
HierarchyBuilder.buildHierarchy(clzz);
}
指定后置条件
正如您所见,指定前置条件其实相当容易,指定后置条件的过程也是一样。例如,如果我想对所有调用 buildHierarchy 的程序保证它不会返回 null 值(这样,这些调用程序不需要再检查这个了),我可以在方法声明之上放置一个 @NotNull 标注,如清单 9 所示:
清单 9. OVal 中的后置条件
@NotNull
public static Hierarchy buildHierarchy(@NotNull Class clzz){
//method body
}
当然,@NotNull 绝不是 OVal 提供的惟一约束条件,但我发现它能非常有效地限制这些令人讨厌的 NullPointerException,或至少能够快速地暴露 它们。
更多的 OVal 约束条件
OVal 也支持在方法调用前或后对类成员进行预先验证。这种机制具有限制针对特定约束条件的重复条件测试的好处,如集合大小或之前讨论过的非 null 的情况。
例如,在清单 10 中,我使用 HierarchyBuilder 定义了一个为类层次构建报告的 Ant 任务。请注意 execute() 方法是如何调用 validate 的,后者会依次验证 fileSet 类成员是否含值;如果不含,会抛出一个异常,因为没有了要评估的类,该报告不能运行。
清单 10. 带条件检验的 HierarchyBuilderTask
public class HierarchyBuilderTask extends Task {
private Report report;
private List fileSet;
private void validate() throws BuildException{
if(!(this.fileSet.size() > 0)){
throw new BuildException("must supply classes to evaluate");
}
if(this.report == null){
this.log("no report defined, printing XML to System.out");
}
}
public void execute() throws BuildException {
validate();
String[] classes = this.getQualifiedClassNames(this.fileSet);
Hierarchy[] hclz = new Hierarchy[classes.length];
try{
for(int x = 0; x < classes.length; x++){
hclz[x] = HierarchyBuilder.buildHierarchy(classes[x]);
}
BatchHierarchyXMLReport xmler = new BatchHierarchyXMLReport(new Date(), hclz);
this.handleReportCreation(xmler);
}catch(ClassNotFoundException e){
throw new BuildException("Unable to load class check classpath! " + e.getMessage());
}
}
//more methods below....
}