`
isiqi
  • 浏览: 16712833 次
  • 性别: Icon_minigender_1
  • 来自: 济南
社区版块
存档分类
最新评论

Hibernate 能够满足我们的验证需求

阅读更多
尽管在 Web 应用程序尽可能多的层次中构建数据验证非常重要,但是这样做却非常耗时,以至于很多开发人员都会干脆忽略这个步骤 —— 这可能会导致今后大量问题的产生。但是随着最新版本的 Java™ 平台中引入了注释,验证变得简单得多了。在本文中,Ted Bergeron 将向您介绍如何使用 Hibernate Annotations 的 Validator 组件在 Web 应用程序中轻松构建并维护验证逻辑。
<!--START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--END RESERVED FOR FUTURE USE INCLUDE FILES-->

有时会有一种工具,它可以真正满足开发人员和架构师的需求。开发人员在第一次下载这种工具当天就可以在自己的应用程序中开始使用这种工具。理论上来说,这种工具在开发人员花费大量时间来掌握其用法之前就可以从中获益。架构师也很喜欢这种工具,因为它可以将开发人员导向更高理论层次的实现。Hibernate Annotations 的 Validator 组件就是一种这样的工具。

开始之前需要了解的内容

在阅读本文之前,应该对 Java 平台版本 5(尤其是注释)、JSP 2.0(因为本文中创建了一些标签文件,并在 TLD 中定义了一些函数,它们都是 JSP 2.0 的新特性)和 Hibernate 及 Spring 框架有一个基本的了解。请注意即使不使用 Hibernate 来实现持久性,也可以在自己的应用程序中使用 Hibernate Validator。

Java SE 5 为 Java 语言提供了很多需要的增强功能,不过其他增强功能可能都不如 注释 这样潜力巨大。使用 注释,我们就终于具有了一个标准、一级的元数据框架为 Java 类使用。Hibernate 用户手工编写 *.hbm.xml 文件已经很多年了(或者使用 XDoclet 来自动实现这个任务)。如果手工创建了 XML 文件,那就必须对每个所需要的持久属性都更新这两个文件(类定义和 XML 映射文档)。使用 HibernateDoclet 可以简化这个过程(请参看清单 1 给出的例子),但是这需要我们确认自己的 HibernateDoclet 版本支持要使用的 Hibernate 的版本。doclet 信息在运行时也是不可用的,因为它被编写到了 Javadoc 风格的注释中了。Hibernate Annotations,如图 2 所示,通过提供一个标准、简明的映射类的方法和所添加的运行时可用性来对这些方式进行改进。


清单 1. 使用 HibernateDoclet 的 Hibernate 映射代码
/**
 * @hibernate.property column="NAME" length="60" not-null="true"
 */
public String getName() {
    return this.name;
}

/**
 * @hibernate.many-to-one column="AGENT_ID" not-null="true" cascade="none" 
 *                        outer-join="false" lazy="true"
 */
public Agent getAgent() {
    return agent;
}
/**
 * @hibernate.set lazy="true" inverse="true" cascade="all" table="DEPARTMENT" 
 * @hibernate.collection-one-to-many class="com.triview.model.Department"
 * @hibernate.collection-key column="DEPARTMENT_ID" not-null="true"
 */
public List<Department> getDepartment() {
    return department;
}


清单 2. 使用 Hibernate Annotations 的 Hibernate 映射代码
@NotNull
@Column(name = "name")
@Length(min = 1, max = NAME_LENGTH) // NAME_LENGTH is a constant declared elsewhere
public String getName() {
    return name;
}

@NotNull
@ManyToOne(cascade = {CascadeType.MERGE }, fetch = FetchType.LAZY)
@JoinColumn(name = "agent_id")
public Agent getAgent() {
    return agent;
}

@OneToMany(mappedBy = "customer", fetch = FetchType.LAZY)
public List<Department> getDepartment() {
    return department;
}

如果使用 HibernateDoclet,那么直到生成 XML 文件或运行时才能捕获错误。使用 注释,在编译时就可以检测出很多错误;或者如果在编辑时使用了很好的 IDE,那么在编辑时就可以检测出部分错误。在从头创建应用程序时,可以利用 hbm2ddl 工具为自己的数据库从 hbm.xml 文件中生成 DDL。一些重要的信息 —— 比如name 属性的最大长度必须是 60 个字符,或者 DDL 应该添加非空约束 —— 都被从 HibernateDoclet 项添加到 DDL 中。当使用注释时,我们可以以类似的方式自动生成 DDL。

尽管这两种代码映射方式都可以使用,不过注释的优势更为明显。使用注释,可以用一些常量来指定长度或其他值。编译循环的速度更快,并且不需要生成 XML 文件。其中最大的优势是可以访问一些有用信息,例如运行时的非空注释或长度。除了清单 2 给出的注释之外,还可以指定一些验证的约束。所包含的部分约束如下:

  • @Max(value = 100)
  • @Min(value = 0)
  • @Past
  • @Future
  • @Email

在适当条件下,这些注释会引起由 DDL 生成检查约束。(显然,@Future 并不是一个适当的条件。)还可以根据需要创建定制约束注释。

验证和应用程序层

编写验证代码是一个烦人且耗时的过程。通常,很多开发人员都会放弃在特定的层进行有效性验证,从而可以节省一些时间;但是所节省的时间是否能够弥补在这个地方因忽略部分功能所引起的缺陷却非常值得探讨。如果在所有应用程序层中创建并维护验证所需要的时间可以极大地减少,那么争论的焦点就会转向是否要在多个层次中进行有效性验证。假设有一个应用程序,它让用户使用一个用户名、密码和信用卡号来创建一个帐号。在这个应用程序中所希望进行验证的组件如下:

  • 视图: 通过 JavaScript 进行验证可以避免与服务器反复进行交互,这样可以提供更好的用户体验。用户可以禁用 JavaScript,因此这个层次的验证最好要有,但是却并不可靠。对所需要的域进行简单的验证是必须的。

  • 控制器: 验证必须在服务器端的逻辑中进行处理。这个层次中的代码可以以适合某个特定用途的方式处理验证。例如,在添加新用户时,控制器可以在进行处理之前检查指定的用户名是否已经存在。

  • 服务: 相对复杂的业务逻辑验证通常都最适合放到服务层中。例如,一旦有一个信用卡对象看起来有效,就应该使用信用卡处理服务对这个信用卡的信息进行确认。

  • DAO: 在数据到达这个层次时,应该已经是有效的了。尽管如此,执行一次快速检查从而确保所需要的域都非空并且值也都在特定的范围或遵循特定的格式(例如 e-mail 地址域就应该包含一个有效的 e-mail 地址)也是非常有益的。在此处捕获错误总比产生可以避免的 SQLException 错误要好。

  • DBMS: 这是通常可以忽略验证的地方。即使当前正在构建的应用程序是数据库的惟一客户机,将来还可能会添加其他客户机。如果应用程序有一些 bug(大部分应用程序都可能会有 bug),那么无效的数据也可能会被发送给数据库。在这种情况中,如果走运,就可以找到无效的数据,并且需要分析这些数据是否可以清除,以及如何清除。

  • 模型: 这是进行验证的一个理想地方,它不需要访问外部服务,也不需要了解持久性数据。例如,某业务逻辑可能会要求用户至少提供一个联系信息,这可以是一个电话号码也可以是一个 e-mail 地址;可以使用模型层的验证来确保用户的确提供了这种信息。

进行验证的一种典型方法是对简单的验证使用 Commons Validator,并在控制器中编写其他一些验证逻辑。Commons Validator 可以生成 JavaScript 来对视图中的验证进行处理。但是 Commons Validator 也有自己的缺陷:它只能处理简单的验证问题,并且将验证的信息都保存到了 XML 文件中。Commons Validator 被设计用来与 Struts 一起使用,而且没有提供一种简单的方法在应用程序层间重用验证的声明。

在规划有效性验证策略时,选择在错误发生时简单地处理这些错误是远远不够的。一种良好的设计同时还要通过生成一个友好的用户界面来防止出现错误。采用预先进行的方法进行验证可以极大地增强用户对于应用程序的理解。不幸的是,Commons Validator 并没有对此提供支持。假设希望 HTML 文件设置文本域的 maxlength 属性来与验证匹配,或者在文本域之后放上一个百分号(%)来表示要输入百分比的值。通常,这些信息都被硬编写到 HTML 文档中了。如果决定修改 name 属性来支持 75 个字符,而不是 60 个字符,那么需要改动多少地方呢?在很多应用程序中,通常都需要:

  • 更新 DDL 来增大数据库列的长度(通过 HibernateDoclet、 hbm.xml 或 Hibernate Annotations)。
  • 更新 Commons Validator XML 文件将最大值增加到 75。
  • 更新所有与这个域有关的 HTML 表单,以修改 maxlength 属性。

更好的方法是使用 Hibernate Validator。验证的定义都被通过注释 添加到了模型层中,同时还有对所包含的验证处理的支持。如果选择充分利用所有的 Hibernate,这个 Validator 就可以在 DAO 和 DBMS 层也提供验证。在下面给出的样例代码中,将使用 reflection 和 JSP 2.0 标签文件多执行一个步骤,从而充分利用注释 为视图层动态生成代码。这可以清除在视图中使用的硬编写的业务逻辑。

在清单 3 中,dateOfBirth 被注释为 NotNull 和过去的日期。 Hibernate 的 DDL 生成代码对这个列添加了一个非空约束,以及一个要求日期必须是之前日期的检查约束。e-mail 地址也是非空的,必须匹配 e-mail 地址的格式。这会生成一个非空约束,但是不会生成匹配这种格式的检查约束。


清单 3. 通过 Hibernate Annotations 进行映射的简单联系方式
/**
 * A Simplified object that stores contact information.
 *
 * @author Ted Bergeron
 * @version $Id: Contact.java,v 1.1 2006/04/24 03:39:34 ted Exp $
 */
@MappedSuperclass
@Embeddable
public class Contact implements Serializable {
    public static final int MAX_FIRST_NAME = 30;
    public static final int MAX_MIDDLE_NAME = 1;
    public static final int MAX_LAST_NAME = 30;

    private String fname;
    private String mi;
    private String lname;
    private Date dateOfBirth;
    private String emailAddress;

    private Address address;

    public Contact() {
        this.address = new Address();
    }

    @Valid
    @Embedded
    public Address getAddress() {
        return address;
    }

    public void setAddress(Address a) {
        if (a == null) {
            address = new Address();
        } else {
            address = a;
        }
    }

    @NotNull
    @Length(min = 1, max = MAX_FIRST_NAME)
    @Column(name = "fname")
    public String getFirstname() {
        return fname;
    }

    public void setFirstname(String fname) {
        this.fname = fname;
    }

    @Length(min = 1, max = MAX_MIDDLE_NAME)
    @Column(name = "mi")
    public String getMi() {
        return mi;
    }

    public void setMi(String mi) {
        this.mi = mi;
    }

    @NotNull
    @Length(min = 1, max = MAX_LAST_NAME)
    @Column(name = "lname")
    public String getLastname() {
        return lname;
    }

    public void setLastname(String lname) {
        this.lname = lname;
    }

    @NotNull
    @Past
    @Column(name = "dob")
    public Date getDateOfBirth() {
        return dateOfBirth;
    }

    public void setDateOfBirth(Date dateOfBirth) {
        this.dateOfBirth = dateOfBirth;
    }

    @NotNull
    @Email
    @Column(name = "email")
    public String getEmailAddress() {
        return emailAddress;
    }

    public void setEmailAddress(String emailAddress) {
        this.emailAddress = emailAddress;
    }

样例应用程序
下载 一节,您可以下载一个样例应用程序,它展示了本文中采用的设计思想和代码。由于这是一个可以工作的应用程序,因此代码比本文中讨论的的内容更为复杂。例如,清单 9 就节选于标签文件 text.tag;这个样例应用程序具有标签文件使用的所有代码,以及其他三个类似的标签文件使用的代码(用于选择、隐藏和检查框的 HTML 元素)。由于这是一个可以工作的应用程序,它包含了一个在这种类型的应用程序中都可以找到的架构。还有一个 Ant 构建文件、Spring 和 Hibernate XML 封装代码,以及 log4j 配置。虽然这些都不是本文介绍的重点,但是您会发现仔细研究一下这个样例应用程序的源代码是非常有用的。

如果需要,Hibernate DAO 实现也可以使用 Validation Annotations。所需做的是在 hibernate.cfg.xml 文件中指定基于 Hibernate 事件的验证规则。(更多信息请参考 Hibernate Validator 的文档;可以在 参考资料 一节中找到相关的链接)。如果真地希望抄近路,您可以只捕获服务或控制器中的 InvalidStateException 异常,并循环遍历 InvalidValue 数组。

对控制器添加验证

要执行验证,需要创建一个 Hibernate 的 ClassValidator 实例。这个类进行实例化的代价可能会很高,因此最好只对希望进行验证的每个类来进行实例化。一种方法是创建一个实用工具类,对每个模型对象存储一个 ClassValidator 实例,如清单 4 所示:


清单 4. 处理验证的实用工具类
/**
 * Handles validations based on the Hibernate Annotations Validator framework.
 * @author Ted Bergeron
 * @version $Id: AnnotationValidator.java,v 1.5 2006/01/20 17:34:09 ted Exp $
 */
public class AnnotationValidator {
    private static Log log = LogFactory.getLog(AnnotationValidator.class);

    // It is considered a good practice to execute these lines once and 
    // cache the validator instances.
    public static final ClassValidator<Customer> CUSTOMER_VALIDATOR =
       new ClassValidator<Customer>(Customer.class);
    public static final ClassValidator<CreditCard> CREDIT_CARD_VALIDATOR =
       new ClassValidator<CreditCard>(CreditCard.class);

    private static ClassValidator<? extends BaseObject> getValidator(Class<? 
      extends BaseObject> clazz) {
        if (Customer.class.equals(clazz)) {
            return CUSTOMER_VALIDATOR;
        } else if (CreditCard.class.equals(clazz)) {
            return CREDIT_CARD_VALIDATOR;
        } else {
            throw new IllegalArgumentException("Unsupported class was passed.");
        }
    }

    public static InvalidValue[] getInvalidValues(BaseObject modelObject) {
        String nullProperty = null;
        return getInvalidValues(modelObject, nullProperty);
    }

    public static InvalidValue[] getInvalidValues(BaseObject modelObject,
       String property) {
        Class<? extends BaseObject>clazz = modelObject.getClass();
        ClassValidator validator = getValidator(clazz);

        InvalidValue[] validationMessages;

        if (property == null) {
            validationMessages = validator.getInvalidValues(modelObject);
        } else {
            // only get invalid values for specified property.  
            // For example, "city" applies to getCity() method.
            validationMessages = validator.getInvalidValues(modelObject, property);
        }
        return validationMessages;
    }
}

在清单 4 中,创建了两个 ClassValidator,一个用于 Customer,另外一个用于 CreditCard。这两个希望进行验证的类可以调用 getInvalidValues(BaseObject modelObject),会返回 InvalidValue[]。否则会返回一个包含模型对象实例错误的数组。另外,这个方法也可以通过提供一个特定的属性名来调用,这样做会只返回与该域有关的错误。

在使用 Spring MVC 和 Hibernate Validator 时,为信用卡创建一个验证过程变得非常简单,如清单 5 所示:


清单 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
 
<?xml version="1.0" encoding="ISO-8859-1" ?>
<taglib xmlns="http://java.sun.com/xml/ns/j2ee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
         http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd"
        version="2.0">

    <tlib-version>1.0</tlib-version>
    <short-name>form</short-name>
    <uri>formtags</uri>

    <tag-file>
        <name>text</name>
        <path>/WEB-INF/tags/form/text.tag</path>
    </tag-file>

<function>
    <description>determine if field is required from Annotations</description>
    <name>required</name>
    <function-class>com.triview.web.Utilities</function-class>
    <function-signature>Boolean required(java.lang.Object,java.lang.String)
    </function-signature>
</function>

</taglib>

清单 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 页面
<%@ taglib tagdir="/WEB-INF/tags/form" prefix="form" %>

<form method="post" action="<c:url value="/signup/customer.edit"/>">

<form:select propertyPath="creditCard.type" collection="${creditCardTypeCollection}" 
  required="true" labelKey="prompt.creditcard.type"/>

<form:text propertyPath="creditCard.number" labelKey="prompt.creditcard.number">
    <img src="<c:url value="/images/icons/help.png"/>" alt="Help" 
      onclick="new Effect.SlideDown('creditCardHelp')"/>
</form:text>

<form:text propertyPath="creditCard.expirationDate"/>
</form>

text.tag 文件的完整源代码太大了,不好放在这儿,因此清单 9 给出了其中关键的部分:


清单 9. 标签文件 text.tag 节选

<%@ attribute name="propertyPath" required="true" %>

<%@ attribute name="size" required="false" type="java.lang.Integer" %>

<%@ attribute name="maxlength" required="false" type="java.lang.Integer" %>

<%@ attribute name="required" required="false" type="java.lang.Boolean" %>



<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>

<%@ taglib uri="formtags" prefix="form" %>



<c:set var="objectPath" value="${form:getObjectPath(propertyPath)}"/>



<spring:bind path="${objectPath}">

    <c:set var="object" value="${status.value}"/>

    <c:if test="${object == null}">

<%-- Bind ignores the command object prefix, so simple properties of the command object

return null above. --%>

        <c:set var="object" value="${commandObject}"/> 

        <%-- We depend on the controller adding this to request. --%>

    </c:if>

</spring:bind>



<%-- If user did not specify whether this field is required, 
query the object for this info. --%>

<c:if test="${required == null}">

    <c:set var="required" value="${form:required(object,propertyPath)}"/>

</c:if>



<c:choose>

    <c:when test="${required == null || required == false}">

        <c:set var="labelClass" value="optional"/>

    </c:when>

    <c:otherwise>

        <c:set var="labelClass" value="required"/>

    </c:otherwise>

</c:choose>



<c:if test="${maxlength == null}">

    <c:set var="maxlength" value="${form:maxLength(object,propertyPath)}"/>

</c:if>



<c:set var="isDate" value="${form:isDate(object,propertyPath)}"/>



<c:set var="cssClass" value="input_text"/>

<c:if test="${isDate}">

    <c:set var="cssClass" value="input_date"/>

</c:if>



<div class="field">

<spring:bind path="${propertyPath}">

<label for="${status.expression}" class="${labelClass}"><fmt:message 

key="prompt.${propertyPath}"/></label>

<input type="text" name="${status.expression}" value="${status.value}" 

id="${status.expression}"<c:if test="${size != null}"> size="${size}"</c:if>

<c:if test="${maxlength != null}"> maxlength="${maxlength}"</c:if>

class="${cssClass}"/>



<c:if test="${isDate}">

    <img id="${status.expression}_button" 

    src="<c:url value="/images/icons/calendar.png"/>" alt="calendar" 

    style="cursor: pointer;"/>

    <script type="text/javascript">

        Calendar.setup( 

        {

            inputField : "${status.expression}", // ID of the input field

            ifFormat : "%m/%d/%Y", // the date format

            button : "${status.expression}_button" // ID of the button

        }

        );

    </script>

</c:if>



<span class="icons"><jsp:doBody/></span> 



<c:if test="${status.errorMessage != null && status.errorMessage != ''}">

    <p class="fieldError"><img id="${status.expression}_error" 

    src="<c:url value="/images/icons/error.png"/>" 

    alt="error"/>${status.errorMessage}</p>

</c:if>



</spring:bind>

</div>


我们马上就可以看出 propertyPath 是惟一需要的属性。sizemaxlengthrequired 都可以忽略。objectPath var 被设置为在 propertyPath 中引用的属性的父对象。因此,如果 propertyPathcustomer.contact.fax.number, 那么 objectPath 就应该被设置为 customer.contact.fax。我们现在就使用 Spring 的 bind 标签绑定到了包含属性的对象上。这会将对象变量设置成对包含属性的实例的引用。接下来,检查这个标签的用户是否已经指定他/她们是否希望属性是必须的。允许表单开发人员覆盖从注释中返回的值是非常重要的,因为有时他/她们希望让控制器为所需要的域设置缺省值,而用户可能并不希望为这个域提供值。如果表单开发人员没有为 required 指定值,那么就可以调用这个表单 TLD 的 required 函数。这个函数调用了在 TLD 文件中映射的方法。这个方法简单地检查 @NotNull 注释;如果它发现某个属性具有这个注释,就将 labelClass 变量设置为必须的。可以类似地确定正确的 maxlength 以及这个域是否是一个 Date

接下来使用 Spring 来绑定到 propertyPath 上,而不是像前面一样只绑定到包含这个属性的对象上。这允许在生成 labelinput HTML 标签时使用 status.expressionstatus.valueinput 标签也可以使用一个大小 maxlength 以及适当的类来生成。如果前面已经确定属性是一个 Date,现在就可以添加 JavaScript 日历了。(可以在 参考资料 一节找到一个很好的日历组件的链接)。注意根据需要链接属性、输入 ID 和图像 ID 的标签是多么简单。)这个 JavaScript 日历需要一个图像 ID 来匹配输入域,其后缀是 _button

最后,可以将 <jsp:doBody/> 封装到一个 span 标签中,这样允许表单开发人员在页面中添加其他图标,例如用来寻求帮助的图标。(清单 8 给出了一个为信用卡号域添加的帮助图标。)最后的部分是检查 Spring 是否为这个属性报告和显示了一个错误,并和一个错误图标一起显示。

使用 CSS,就可以对必须的域进行一下装饰 —— 例如,让它们以红色显示、在文本边上显示一个星号,或者使用一个背景图像来装饰它。在清单 10 中,将必须的域的标签设置成黑色,而且后面显示一个红色的星号(在 Firefox 以及其他标准兼容的浏览器中),如果是在 IE 中则还会在左边加上一个小旗子的背景图像:


清单 10. 对必须域进行装饰的 CSS 代码
label.required {
    color: black;
    background-image: url( /images/icons/flag_red.png );
    background-position: left;
    background-repeat: no-repeat;
}
label.required:after {
    content: '*';
}
label.optional {
    color: black;
}

日期输入域自动会在右边放上一个 JavaScript 日历图标。对所有的文本域设置正确的 maxlength 属性可以防止用户输入太多文本所引起的错误。可以扩展 text 标签来为输入域类设置其他的数据类型。可以修改 text 标签使用 HTML,而不是 XHTML(如果希望这样)。可以不太费力地获得具有正确语义的 HTML 表单,而且不需学习基于组件的框架知识,就可以利用基于组件的 Web 框架的优点。

尽管标签文件生成的 HTML 文件可以帮助防止一些错误的产生,但是在视图层并没有任何代码来真正进行错误检查。由于可以使用类属性,现在就可以添加一些简单的 JavaScript 来实现这种功能了,如清单 11 所示。这里的 JavaScript 也可以是通用的,在任一表单中都可以重用。


清单 11. 简单的 JavaScript 验证程序
<script type="text/javascript">
    function checkRequired(form) {
        var requiredLabels = document.getElementsByClassName("required", form);
        for (i = 0; i < requiredLabels.length; i++) {

var labelText = requiredLabels[i].firstChild.nodeValue; // Get the label's text
var labelFor = requiredLabels[i].getAttribute("for"); // Grab the for attribute
var inputTag = document.getElementById(labelFor); // Get the input tag

            if (inputTag.value == null || inputTag.value == "") {
                alert("Please make sure all required fields have been entered.");
                return false; // Abort Submit
            }
        }
        return true;
    }
</script>

这个 JavaScript 是通过为表单声明添加 onsubmit="return checkRequired(this);" 被调用的。这个脚本简单地获取具有所需要的类的表单中的所有元素。由于我们的习惯是在标签标记中使用这个类,因此代码会通过 for 属性来查找与这个标签连接在一起的输入域。如果任何输入域为空,就会生成一条简单的警告消息,表单提交就会取消。可以简单地对这个脚本进行扩充,使其扫描多个类,并相应地进行验证。

对于基于 JavaScript 的综合的验证集合来说,最好是使用开源实现,而不是自行开发。在清单 8 中您可能已经注意到下面的代码:

 onclick="new Effect.SlideDown('creditCardHelp')" 

这个函数是 Script.aculo.us 库的一部分,这个库提供了很多高级的效果。如果正在使用 Script.aculo.us,就需要对所构建的内容使用 Prototype 库。 JavaScript 验证库的一个例子是由 Andrew Tetlaw 在 Prototype 基础上构建的。(请参看 参考资料 一节中的链接。)他的框架依赖于被添加到输入域的类:

<input class="required validate-number" id="field1" name="field1" />

可以简单地修改 text.tag 的逻辑在 input 标签中插入几个类。将 class="required" 添加到输入标签和 label 标签中不会影响 CSS 规则,但会破坏清单 10 中给出的简单 JavaScript 验证程序。如果要混合使用框架中的代码和简单的 JavaScript 代码,最好使用不同的类名,或在使用类名搜索元素时确保类名有效并检查标签类型。





回页首


最后的考虑

本文已经介绍了模型层的注释如何充分用来在视图、控制器、DAO 和 DBMS 层中创建验证。必须手工创建服务层的验证,例如信用卡验证。其他模型层的验证,例如强制属性 C 是必须的,而属性 A 和 B 都处于指定的状态,这也是一个手工任务。然而,使用 Hibernate Annotations 的 Validator 组件,就可以轻松地声明并应用大多数验证。

展望

不论是简单例子还是所引用框架的 JavaScript 验证都可以对简单的条件进行检查,例如域必须要填写,或者客户机端代码中的数据类型必须要匹配预期的类型。需要用到服务器端逻辑的验证可以使用 Ajax 添加到 JavaScript 验证程序中。您可以使用一个用户注册界面来让用户可以选择用户名。文本标签可以进行增强来检查 @Column(unique = true) 注释。在找到这个注释时,标签可以添加一个用来触发 Ajax 调用的类。

现在您不需要在应用程序层间维护重复的验证逻辑了,这样就可以节省出大量的开发时间。想像一下您最终可以为应用程序所能添加的增强功能!






回页首


下载

描述 名字 大小 下载方法 示例应用程序
j-hibval-source.zip 8MB HTTP
关于下载方法的信息 Get Adobe® Reader®


参考资料

学习

获得产品和技术

讨论


关于作者

Ted Bergeron 是 Triview 的合作创始人之一,Triview 是一家企业软件咨询公司,位于加利福尼亚的圣地亚哥。Ted 从事基于 Web 的应用程序的设计已经有十 多年的时间了。他所做过的一些知名的项目包括为 Sybase、Orbitz、Disney 和 Qualcomm 所设计的项目。Ted 还曾用三 年的时间作为一名技术讲师来教授有关 Web 开发、Java 开发和数据库逻辑设计的课程。您可以在 Triview 的 Web 站点 上了解有关 Triview 公司的更多内容,或者也可以拨打该公司的免费电话 (866)TRIVIEW。

分享到:
评论

相关推荐

    最新更新!!!上市公司-动态能力数据(2012-2023年)

    ## 01、数据介绍 动态能力理论最早由提斯(Teece)与皮萨洛(Pisano)于1994年正式提出,他们将动态能力定义为“能够创造新产品和新过程,以及对变化的市场环境做出响应的一系列能力”。 动态能力具体体现在吸收能力、创新能力和适应能力三个方面。这些能力使公司能够快速适应市场变化,抓住新的商业机会,从而保持或提升竞争优势。 数据名称:上市公司-动态能力数据 数据年份:2012-2023年 ## 02、相关数据及指标 证券代码 证券名称 年份 Symbol RD IA ACV DC

    使用JavaWeb技术实现文件的上传、下载.zip(毕设&课设&实训&大作业&竞赛&项目)

    项目工程资源经过严格测试运行并且功能上ok,可实现复现复刻,拿到资料包后可实现复现出一样的项目,本人系统开发经验充足(全栈全领域),有任何使用问题欢迎随时与我联系,我会抽时间努力为您解惑,提供帮助 【资源内容】:包含源码+工程文件+说明等。答辩评审平均分达到96分,放心下载使用!可实现复现;设计报告也可借鉴此项目;该资源内项目代码都经过测试运行,功能ok 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 【提供帮助】:有任何使用上的问题欢迎随时与我联系,抽时间努力解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 下载后请首先打开说明文件(如有);整理时不同项目所包含资源内容不同;项目工程可实现复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用,资源为网络商品(电子资料类)基于网络商品和电子资料商品的性质和特征不支持退款,质量优质,放心下载使用

    基于百度EasyDL训练的模型,并可以部署在前端、PC,移动端和微信小程序端,视频流推断.zip(毕设&课设&实训&大作业&竞赛&项目)

    项目工程资源经过严格测试运行并且功能上ok,可实现复现复刻,拿到资料包后可实现复现出一样的项目,本人系统开发经验充足(全栈全领域),有任何使用问题欢迎随时与我联系,我会抽时间努力为您解惑,提供帮助 【资源内容】:包含源码+工程文件+说明等。答辩评审平均分达到96分,放心下载使用!可实现复现;设计报告也可借鉴此项目;该资源内项目代码都经过测试运行,功能ok 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 【提供帮助】:有任何使用上的问题欢迎随时与我联系,抽时间努力解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 下载后请首先打开说明文件(如有);整理时不同项目所包含资源内容不同;项目工程可实现复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用,资源为网络商品(电子资料类)基于网络商品和电子资料商品的性质和特征不支持退款,质量优质,放心下载使用

    随机配置网络模型SCN多输入单输出拟合预测建模与效果评估图集展示,随机配置网络模型SCN多输入单输出拟合预测建模工具-直观输出预测图与评价报告,随机配置网络模型SCN做多输入单输出的拟合预测建模

    随机配置网络模型SCN多输入单输出拟合预测建模与效果评估图集展示,随机配置网络模型SCN多输入单输出拟合预测建模工具——直观输出预测图与评价报告,随机配置网络模型SCN做多输入单输出的拟合预测建模。 程序内注释详细直接替数据就可以使用。 程序语言为matlab。 程序直接运行可以出拟合预测图,迭代优化图,线性拟合预测图,多个预测评价指标。 PS:以下效果图为测试数据的效果图,主要目的是为了显示程序运行可以出的结果图,具体预测效果以个人的具体数据为准。 2.由于每个人的数据都是独一无二的,因此无法做到可以任何人的数据直接替就可以得到自己满意的效果。 ,核心关键词: 随机配置网络模型SCN; 多输入单输出拟合预测建模; 程序内注释; MATLAB程序; 拟合预测图; 迭代优化图; 线性拟合预测图; 预测评价指标。,Matlab下的多输入单输出网络模型预测建模及评价指标体系构建

    主应力方向破坏:深入解析comsol水力压裂的力学机制,comsol技术解析:水力压裂主应力方向与岩层破坏之探究,comsol水力压裂,主应力方向的破坏 ,comsol水力压裂; 主应力方向; 破坏

    主应力方向破坏:深入解析comsol水力压裂的力学机制,comsol技术解析:水力压裂主应力方向与岩层破坏之探究,comsol水力压裂,主应力方向的破坏 ,comsol水力压裂; 主应力方向; 破坏; 裂缝扩展。,水力压裂主应力破坏研究

    基于碳捕集电厂低碳特性的多时间尺度综合能源系统调度模型与优化算法研究,基于碳捕集电厂低碳特性与综合能源系统的多时间尺度电热调度模型 ,计及碳捕集电厂低碳特性 需求响应 综合能源系统 多时

    基于碳捕集电厂低碳特性的多时间尺度综合能源系统调度模型与优化算法研究,基于碳捕集电厂低碳特性与综合能源系统的多时间尺度电热调度模型。,计及碳捕集电厂低碳特性 需求响应 综合能源系统 多时间尺度调度模型 MATLAB代码: 关键词:碳捕集电厂 综合灵活运行方式 需求响应 日前调度 实时调度 参考文档:《计及碳捕集电厂低碳特性的含风电电力系统源-荷多时间尺度调度方法》非完全复现,只做了日前日内部分,并在上述基础上改进升级为电热综合电源系统 仿真平台:MATLAB yalmip+cplex 主要内容:代码主要做的是一个电厂 微网多时间尺度电热综合能源系统低碳经济调度模型,源侧在碳捕集电厂中装设烟气旁路系统与溶液存储器,形成碳捕集电厂综合灵活运行方式进而与风电协调配合;荷侧调用不同响应速度的价格型、激励型需求响应资源克服多时间尺度下碳捕集电厂综合灵活运行方式的局限,通过源荷资源协调优化,从而提高系统的低碳性能。 其次,构建源荷协调的日前-日内两阶段低碳经济调度模型,优化系统的负荷及分配计划。 ,关键词:碳捕集电厂; 综合灵活运行方式; 需求响应; 日前调度; 实时调

    chromedriver-mac-arm64-135.0.7031.0(Canary).zip

    chromedriver-mac-arm64-135.0.7031.0(Canary).zip

    基于百度语音和图灵机器人实现的智能在线语音聊天 ,web端视音频录制.zip(毕设&课设&实训&大作业&竞赛&项目)

    项目工程资源经过严格测试运行并且功能上ok,可实现复现复刻,拿到资料包后可实现复现出一样的项目,本人系统开发经验充足(全栈全领域),有任何使用问题欢迎随时与我联系,我会抽时间努力为您解惑,提供帮助 【资源内容】:包含源码+工程文件+说明等。答辩评审平均分达到96分,放心下载使用!可实现复现;设计报告也可借鉴此项目;该资源内项目代码都经过测试运行,功能ok 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 【提供帮助】:有任何使用上的问题欢迎随时与我联系,抽时间努力解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 下载后请首先打开说明文件(如有);整理时不同项目所包含资源内容不同;项目工程可实现复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用,资源为网络商品(电子资料类)基于网络商品和电子资料商品的性质和特征不支持退款,质量优质,放心下载使用

    基于python+vue开发的网上商城系统

    1. 网上商城系统 这是一个基于python+vue开发的商城网站,平台采用B/S结构,后端采用主流的Python语言进行开发,前端采用主流的Vue.js进行开发。 整个平台包括前台和后台两个部分。 前台功能包括:首页、商品详情页、用户中心模块。 后台功能包括:总览、订单管理、商品管理、分类管理、标签管理、评论管理、用户管理、运营管理、日志管理、系统信息模块。

    MATLAB环境下基于振动信号的旋转机械状态监测与预测算法研究与应用拓展,MATLAB环境下基于振动信号的旋转机械状态监测与预测算法研究与应用拓展,MATLAB环境下使用振动信号对旋转机械进行状态监测

    MATLAB环境下基于振动信号的旋转机械状态监测与预测算法研究与应用拓展,MATLAB环境下基于振动信号的旋转机械状态监测与预测算法研究与应用拓展,MATLAB环境下使用振动信号对旋转机械进行状态监测和预测。 算法运行环境为MATLAB r2021b,从滚动轴承的振动信号中提取特征、进行状态监测和预测。 算法可迁移至金融时间序列,地震 微震信号,机械振动信号,声发射信号,电压 电流信号,语音信号,声信号,生理信号(ECG,EEG,EMG)等一维时间序列信号。 ,MATLAB环境; 振动信号; 旋转机械状态监测与预测; 滚动轴承; 特征提取; 一维时间序列信号。,MATLAB算法:一维时间序列振动信号的旋转机械状态监测与预测

    玩转 Python @property:让属性和方法毫不费力地融合

    @property 是 Python 中的装饰器,用于将方法转换为属性,使得访问属性时无需调用方法。它让代码更加简洁、优雅,同时保持封装性,提升代码的可读性和维护性。

    springboot企业数据管理平台类社会服务平台源码+论文+视频1.zip

    springboot企业数据管理平台类社会服务平台源码+论文+视频1

    5G智慧社区项目智能化解决方案PPT(28页).pptx

    智慧社区,作为智慧城市的重要组成部分,正逐步改变着我们的生活方式。近年来,随着国家政策的不断推动,智慧社区建设进入了快速发展阶段。2014年,国务院发布的《关于促进智慧城市健康发展的指导意见》为智慧城市建设指明了方向,而随后的《关于加强和完善城乡社区治理的意见》及《绿色社区创建行动方案》等文件,进一步加速了智慧社区的建设步伐。据预测,2020年智慧社区市场规模已突破6000亿元,展现出巨大的市场潜力和发展空间。 智慧社区的建设,主要依赖于物联网、云计算、大数据等先进技术的融合应用。这些技术不仅提升了社区的安全保障、应急响应和设备监管能力,还实现了社区管理的智能化和高效化。通过物联网技术,智慧社区能够实时监测社区内的各项设施,如智慧照明、水泵房监测、配电监测等,确保社区运行的安全和稳定。同时,借助云计算和大数据技术,智慧社区可以整合各类传感器资源,为社区提供统一的云计算和大数据服务,构建“云+端”的立体化服务体系。这种服务模式不仅提高了社区管理的效率,还为居民带来了更加便捷、舒适的生活体验。 在智慧社区中,各种创新应用层出不穷。例如,智慧安防系统可以实时监测井盖异动、应急通道违停等情况,确保社区的安全;智慧环境监测系统则能够实时监测垃圾桶满溢、天气情况等信息,为社区的环保和卫生工作提供有力支持。此外,智慧社区还通过数据分析与挖掘技术,针对不同人、物、事、场景进行深层次分析,助力全面管控与优化系统与监管机制。这些创新应用不仅提升了社区的管理水平,还为居民创造了一个更加绿色、健康、安全、舒适、低碳、便捷的生活环境。随着技术的不断进步和应用场景的不断拓展,智慧社区的未来将更加美好,为城市居民带来更加智能、便捷的生活方式。

    从 ID3 到 CART:一文看懂决策树核心原理,实战 Titanic 乘客生存预测

    cart,cd3, c4.5 决策树代码和可视化,数据,完整资源

    双有源桥扩展技术:100kHz高频率、最小电流应力优化的3kw功率开关控制系统,双有源桥扩展技术:100kHz高频率、375-48V宽电压范围下的3kw功率控制与优化,双有源桥扩展移相控制 开关频率

    双有源桥扩展技术:100kHz高频率、最小电流应力优化的3kw功率开关控制系统,双有源桥扩展技术:100kHz高频率、375-48V宽电压范围下的3kw功率控制与优化,双有源桥扩展移相控制 开关频率100k,375-48V,功率3kw 控制方式为最小电流应力优化控制 参数可定制 ,双有源桥;扩展移相控制;开关频率100k;功率3kw;最小电流应力优化控制;参数可定制,双桥移相控制,最小电流应力优化——100k开关频率功率控制技术

    最新更新!!!2024年HS编码出口退税率数据(2004-2024年)

    ## 01、数据简介 出口退税率是针对出口产品在国内已缴纳的税款,在货物报关出口后退还给出口企业时,按照一定比例计算的退税金额与计税价格之间的比率。 出口退税率是出口退税制度中的一个重要参数,它体现了国家对出口企业的税收优惠政策,有助于降低企业的出口成本,提升其在国际市场上的竞争力。同时,国家也会根据经济形势和国际贸易的变化,适时调整出口退税率,以更好地服务于国家的经济发展战略。 数据名称:2024年HS编码出口退税率数据 数据年份:2004-2024年 ## 02、相关数据 CODE、ST_DATE、END_DATE、ZHCMCODE、NAME、DWCODE、UNIT、BCFLAG、STDFLAG、DWFLAG、SZ、ZSSL_SET、CLDE、CJDL、TSL、SPLB、TSFLAG、NOTE。 ## 03、数据截图

    最近比较火的虎年西游记金钱豹头像制作小程序源码.zip

    最近比较火的虎年西游记金钱豹头像制作小程序源码 今天为大家分享一下最近抖音和朋友圈比较火的云开发虎年西游记金钱豹头像制作小程序源码和西游记南山大王金钱豹的趣味头像素材包,年少不知豹哥好,错把樵哥当成宝,金钱豹头像最近也是由于谐音梗金钱暴富为大家所知晓,加上短视频和朋友圈的推波助澜下,成功让这个人物火出了圈,由此也诞生了金钱豹头像制作小程序和网站,今天先给大家分享一下小程序的源码,还是虎年风格的金钱豹头像制作小程序源码,加上浓浓的新年氛围,金钱豹头像喜庆边框,简约可爱,内置十八个金钱豹头像素材,选中金钱豹头像和边框点击保存就可以去换头像啦。

    PNP发射极接地开关仿真原理图

    PNP发射极接地开关仿真原理图

    风电-光热-生物质混合电站鲁棒优化调度模型:最大化利润与应对不确定性的策略,风电-光热-生物质混合电站的鲁棒优化调度模型:最大化利润并平衡不确定性风险,风电-光热-生物质混合电站鲁棒优化调度模型 摘要

    风电-光热-生物质混合电站鲁棒优化调度模型:最大化利润与应对不确定性的策略,风电-光热-生物质混合电站的鲁棒优化调度模型:最大化利润并平衡不确定性风险,风电-光热-生物质混合电站鲁棒优化调度模型 摘要:为解决混合电站参与电力市场运行问题,从混合电站的结构及运行机理出发,提出了电力市场下风电-光热-生物质混合电站鲁棒优化调度模型。 该模型以最大化运行利润为目标函数,考虑了混合电站参与电力市场所获收益、并网运行环境效益、弃风弃光惩罚成本、各组成部分运行维护成本、系统运行约束等因素。 针对混合电站运行面临的不确定性和由此带来的风险问题,采用鲁棒优化方法处理风电功率、光热功率、负荷及电力市场价格的不确定性,并建立风险量化指标,平衡系统的鲁棒性与经济性,为混合电站运营商提供决策依据。 本代码为参考文献而原创代码 ,核心关键词:混合电站; 鲁棒优化; 调度模型; 电力市场; 运行利润; 不确定性; 风险量化; 风电-光热-生物质,电力市场下混合能源电站鲁棒优化调度模型研究

    springboot在线知识管理平台类智慧交通调度平台源码+论文+视频1.zip

    springboot在线知识管理平台类智慧交通调度平台源码+论文+视频1

Global site tag (gtag.js) - Google Analytics