清单 5. Spring MVC 控制器使用的 CreditCardValidator
/** * Performs validation of a CreditCard in Spring MVC. * * @author Ted Bergeron * @version $Id: CreditCardValidator.java,v 1.2 2006/02/10 21:53:50 ted Exp $ */public class CreditCardValidator implements Validator {    private CreditCardService creditCardService;    public void setCreditCardService(CreditCardService service) {        this.creditCardService = service;    }    public boolean supports(Class clazz) {        return CreditCard.class.isAssignableFrom(clazz);    }    public void validate(Object object, Errors errors) {        CreditCard creditCard = (CreditCard) object;        InvalidValue[] invalids = AnnotationValidator.getInvalidValues(creditCard);                // Perform "expensive" validation only if no simple errors found above.        if (invalids == null || invalids.length == 0) {             boolean validCard = creditCardService.validateCreditCard(creditCard);            if (!validCard) {                errors.reject("error.creditcard.invalid");            }        } else {            for (InvalidValue invalidValue : invalids) {                errors.rejectValue(invalidValue.getPropertyPath(),                   null, invalidValue.getMessage());            }        }    }}
    validate() 方法只需要将 creditCard 实例传递给这个验证过程,从而返回 InvalidValue 数组。如果发现了一个或多个这种简单错误,那么就可以将 Hibernate 的 InvalidValue 数组转换成 Spring 的 Errors 对象。如果用户已经创建了这个信用卡并且没有出现任何简单错误,就可以将更加彻底的验证委托给服务层进行。这一层可以与商业服务提供者一起对信用卡进行验证。    现在我们已经看到这个简单的模型层注释是如何平衡到控制器、DAO 和 DBMS 层的验证的。在 HibernateDoclet 和 Commons Validator 中发现的验证逻辑的重合现在都已经统一到模型中了。尽管这是一个非常受欢迎的改进,但是视图层传统上来说一直是最需要进行详细验证的地方。为视图添加验证    在下面的例子中,使用了 Spring MVC 和 JSP 2.0 标签文件。JSP 2.0 允许在 TLD 文件中对定制函数进行注册,并在一个标签文件中进行调用。标签文件类似于 taglibs,但是它们是使用 JSP 代码编写的,而不是使用 Java 代码编写的。采用这种方法,使用 Java 语言写好的代码就可以封装成函数,而使用 JSP 写好的代码则可以放入标签文件中。在这种情况中,对注释的处理需要使用映像,这会由几个函数来执行。绑定 Spring 或呈现 XHTML 的代码也是标签文件的一部分。清单 6 中节选的 TLD 代码定义 text.tag 文件可以使用,并定义了一个名为 required 的函数。
清单 6. 创建表单 TLD
     1.0    form    formtags            text        /WEB-INF/tags/form/text.tag        determine if field is required from Annotations    required    com.triview.web.Utilities    Boolean required(java.lang.Object,java.lang.String)    
     清单 7 节选自 Utilities 类,其中包含了标签文件使用的所有函数。在前文中我们曾经说过,最适合使用 Java 代码编写的代码都被放到了几个 TLD 可以映射的函数中,这样标签文件就可以使用它们了;这些函数都是在 Utilities 类中进行编码的。因此,我们需要三样东西:定义这些类的 TLD 文件、Utilities 中的函数,以及标签文件本身,后者要使用这些函数。(第四样应该是使用这个标签文件的 JSP 页面。)    在清单 7 中,给出了在 TLD 中引用的函数和另外一个表示给定属性是否是 Date 的方法。在这个类中要涉及到比较多的代码,但是本文限于篇幅,不会给出所有的代码;不过需要注意 findGetterMethod() 除了将表达式语言(Expression Language,EL)方法表示(customer.contact)转换成 Java 表示(customer.getContact())之外,还执行了基本的映像操作。
清单 7. Utilities 节选 public static Boolean required(Object object, String propertyPath) { Method getMethod = findGetterMethod(object, propertyPath); if (getMethod == null) { return null; } else { return getMethod.isAnnotationPresent(NotNull.class); } } 
public static Boolean isDate(Object object, String propertyPath) { return java.util.Date.class.equals(getReturnType(object, propertyPath)); } 
public static Class getReturnType(Object object, String propertyPath) { Method getMethod = findGetterMethod(object, propertyPath); if (getMethod == null) { return null; } else { return getMethod.getReturnType(); } } 
此处可以清楚地看到在运行时使用 Validation annotations 是多么容易。可以简单地引用对象的 getter 方法,并检查这个方法是否有相关的给定的注释 。 
清单 8 中给出的 JSP 例子进行了简化,这样就可以着重查看相关的部分了。此处,这里有一个表单,它有一个选择框和两个输入域。所有这些域都是通过在 form.tld 文件中声明的标签文件进行呈现的。标签文件被设计成使用智能缺省值,这样就可以根据需要允许简单编码的 JSP 可以有定义更多信息的选项。关键的属性是 propertyPath,它使用 EL 符号将这个域映射为模型层属性,就像是使用 Spring MVC 的 bind 标签一样。 清单 8. 一个包含表单的简单 JSP 页面 "> 
required="true" labelKey="prompt.creditcard.type"/> " alt="Help" onclick="new Effect.SlideDown('creditCardHelp')"/> 
text.tag 文件的完整源代码太大了,不好放在这儿,因此清单 9 给出了其中关键的部分: 清单 9. 标签文件 text.tag 节选 key="prompt.${propertyPath}"/> 
 id="${status.expression}" size="${size}" 
maxlength="${maxlength}" 
class="${cssClass}"/> 
 src="" alt="calendar" 
style="cursor: pointer;"/> src="" 
alt="error"/>${status.errorMessage}
我们马上就可以看出 propertyPath 是惟一需要的属性。size、 maxlength 和 required 都可以忽略。objectPath var 被设置为在 propertyPath 中引用的属性的父对象。因此,如果 propertyPath 是 customer.contact.fax.number, 那么 objectPath 就应该被设置为 customer.contact.fax。我们现在就使用 Spring 的 bind 标签绑定到了包含属性的对象上。这会将对象变量设置成对包含属性的实例的引用。接下来,检查这个标签的用户是否已经指定他/她们是否希望属性是必须的。允许表单开发人员覆盖从注释中返回的值是非常重要的,因为有时他/她们希望让控制器为所需要的域设置缺省值,而用户可能并不希望为这个域提供值。如果表单开发人员没有为 required 指定值,那么就可以调用这个表单 TLD 的 required 函数。这个函数调用了在 TLD 文件中映射的方法。这个方法简单地检查 @NotNull 注释;如果它发现某个属性具有这个注释,就将 labelClass 变量设置为必须的。可以类似地确定正确的 maxlength 以及这个域是否是一个 Date。