随笔-314  评论-209  文章-0  trackbacks-0
 

摘要:

本文根据BJUG maillist讨论整理,取自Buffalo网站,在此对Michael的贡献表示感谢!文中引用的评论仅表示个人观点,供大家作为技术参考。先敬佩Michael一个,能做到这样,Michael付出了很多。下文简单比较一下Buffalo和DWR,两者的思路基本类似,有很多共性的东西。总的看来,Buffalo能满足基本的需要,但DWR已经在整体系统结构上有了更多优势...
Buffalo VS DWR

作者:cleverpig

声明:文中引用的评论仅表示个人观点,供大家作为技术参考。


开篇言:

    本文根据BJUG maillist讨论整理,取自Buffalo网站,在此对Michael的贡献表示感谢!文中引用的评论仅表示个人观点,供大家作为技术参考。先敬佩Michael一个,能做到这样,Michael付出了很多。下文简单比较一下Buffalo和DWR,两者的思路基本类似,有很多共性的东西。总的看来,Buffalo能满足基本的需要,但DWR已经在整体系统结构上有了更多优势。框架的生命周期是有限的,如果不能与时俱进,将迟早面临被淘汰的局面。

版权声明:任何获得Matrix授权的网站,转载时请务必保留以下作者信息和链接
作者:cleverpig;cleverpig
原文:http://www.matrix.org.cn/resource/article/2006-10-18/Buffalo+DWR_4ebd1e01-5e90-11db-a5c2-7f23a8944cb0.html
关键字:buffalo,dwr,ajax,比较

一、两个Ajax框架:

image

    Buffalo是一个为java web项目而设计的轻量级AJAX框架。它是开源的(Apache License 2.0),其功能强大且易用。主要解决在j2ee ajax开发中的常见问题。它承诺为开发者和最终用户都能受益。最重要的Buffalo的作者Michael Chen是位中国开发者,他就生活在北京。

image

    DWR是一个Java开源库,帮助你实现Ajax网站。它可以让你在浏览器中的Javascript代码调用Web服务器上的Java,就像在Java代码就在浏览器中一样。DWR会根据你的Java类动态的生成Javascript代码。这些代码的魔力是让你感觉整个Ajax调用都是在浏览器上发生的,但事实上是服务器执行了这些代码,DWR负责数据的传递和转换。

二、共性:

    支持Spring集成:均支持。

    使用一个Servlet来接收所有的AJAX请求:
        Buffalo使用ApplicationServlet<————>DWR使用DWRServlet。

    使用XMLHttpRequest + JavaScript 传输数据:
        Buffalo使用buffalo.js<————>DWR使用dwr.js & util.js。

    在Java和Javascript之间转换数据:
        Buffalo使用marshallingContext<————>DWR使用Converter。

    协议:
        Buffalo在1.2.x之前采用burlap, Buffalo解析大数据量可能会比较慢,然而可以适用于多种服务器端和客户端,并且burlap协议的完整性和支持的数据类型更加丰富。2.0开始采用自定义的基于xml的协议(来自burlap,做了更适合web的修改),并自行编写了解析器,性能更高。

        DWR使用自定义的简单文本协议。


三、Buffalo特性:

   1. 基于prototype:如果你的AJAX应用也是基于prototype,那么可以减少重复加载prototype的带宽,并且获得相当一致的编程概念。

   2. Bind:提供了对结果数据的处理,直接将数据绑定到页面对象并展示,这是一个动人的特性。(DWR在Util.js中也提供了一些方法来简化数据的展示,但不如 Buffalo做的更多。) 在2.0中,Bind能力更加强大,能够将值直接绑定到表单元素、表格、DIV/Span、甚至整个表单上。关键是这种绑定是无侵入并且与buffalo 整体结构完全整合,对外表现只有一个简单的buffalo.bindReply或者Buffalo.Bind.bind即可。http://buffalo.sourceforge.net/binding.html有一些描述。

   3. 序列化:Buffalo支持任意对象,任意深度,任意数据结构的java到javascript以及javascript到java的双向序列化。并且支持引用。这里有完整的协议说明。由于文档和演示不充分,很多人以为buffalo不支持任意对象了 ~

   4. 生命周期对象访问:1.2.4之前需要继承一个BuffaloService,
    从1.2.4开始就不需要继承了,引入了线程安全的BuffaloContext对象,只需要通过BuffaloContext.getContext()即可获得一个线程安全的引用,并且对Request的各种属性进行操作。更方便的是:

      Map BuffaloContext.getContext.getSession()
      Map BuffaloContext.getContext.getApplication()
      Map BuffaloContext.getContext.getCookie()

      即可获得session/application/cookie的Map,操作这些Map即可获得对这些生命周期的各种变量进行查询和更新。这个特性参考了webwork中ActionContext的设计。

   5. 对Collection/Array的模糊处理:buffalo中提供了对Collection/Array对象的模糊识别能力。例如:服务器端有一个方法需要List参数,客户端传递过去一个javascript数组就可以了,不需要费心的组装对象。buffalo通过这些很细小的地方来提高程序员生产力。

   6. 客户端组装对象:据报告DWR只支持简单数据类型作为参数在客户端传入。buffalo支持在客户端组装对象,甚至可以直接将整个表单序列化为一个对象作为参数传给远程客户端。DWR协议天生不足,这方面,我猜想它完全没有能力。

   7. 对重载方法的处理能力:由于java与javascript之间类型的不匹配,DWR的代码生成无法对重载方法进行处理。例如,sum(double,double), sum(int, int) DWR很可能不知道你要调用哪一个。从2.0开始buffalo支持了对重载的处理。

四、DWR特性:

   1. 支持Batch:可以将多个Service函数调用放在一个XMLHttpRequest请求中完成。

   Michael Chen评论:我一直认为这不是一个好的实践。在客户端发起多个请求并获得响应除了获得编程的复杂外,还增加了服务器端设计service的自由度。这种方式感觉上更鼓励为远程调用设计细粒度的服务、将组装逻辑放在客户端。这种设计风格我不太喜欢,因此batch也一直没有考虑实现,虽然实现不太麻烦。

   2. Converter:可以转换任意类型的Java对象到JavaScript,并允许直接使用。例如:Customer类包含一个address变量,当AjaxCall返回Customer对象的时候,可以直接在Javascript中使用customer.address来获得Address的信息。

   3. HttpServlet:支持在被调用的Service方法中获得HttpServletRequest和HttpServletResponse对象,这样可以访问当前Session中的数据。

   4. 允许Expose部分函数和属性。(Buffalo无限制,可以访问Service中的任意函数。)

   Michael Chen评论: 这个我也考虑过...DWR的代码生成机制使得它不得不通过这种方式减小些流量。Buffalo如果想实现这个特性也不是不行,只是我觉得,既然 Service辛辛苦苦实现了,还需要通过这种方式来让别人不能用么?况且buffalo没有代码生成,无论你暴露多少方法流量都是一样的。考虑到实际情况,buffalo没有实现这个特性。

   5. DWR2.0中提出了Reverse Ajax:提供在Java代码中来处理页面上元素的功能。

   Michael Chen评论: 这个东东...也还是代码生成的trick...然而我的态度是javascript与java同样重要的,因此不会让代码生成类的东西破坏javascript的整体性。


五、相关资源:

image

   在过去的两年间,baffalo的开发者拥有值得兴奋的积极反馈:buffalo非常适于在java web项目的ajax开发。为了帮助更多的用户更好地使用buffalo,他们期待你的帮助:发布文档、bug报告和bug修正等。

   baffalo Maillist: buffalo-users@lists.sourceforge.net

   baffalo 论坛:http://groups.google.com/group/amowa

image
cleverpig写的Buffalo的最佳实践
posted @ 2006-10-19 15:31 xzc 阅读(414) | 评论 (0)编辑 收藏
     摘要: 在上一篇文章《为Struts 2.0做好准备》中,我过于详细地介绍了Struts 2.0开发环境和运行环境的配置,所以,本文很少涉及的以上两方面的细节。如果,您看完《为Struts 2.0做好准备》后,还有什么不明白,或者没法运行文中例子,请联系我。我的E-MAIL:Max.M.Yuan@gmail.com。 在介绍常用标志前,我想先从总体上,对Struts 1.x与Struts 2.0的标...  阅读全文
posted @ 2006-10-19 15:12 xzc 阅读(328) | 评论 (0)编辑 收藏

注释简化了数据验证

developerWorks
文档选项
将此页作为电子邮件发送

将此页作为电子邮件发送

未显示需要 JavaScript 的文档选项

样例代码


拓展 Tomcat 应用

下载 IBM 开源 J2EE 应用服务器 WAS CE 新版本 V1.1


级别: 中级

Ted Bergeron (ted@triview.com), 合作创始人, Triview, Inc.

2006 年 10 月 10 日

尽管在 Web 应用程序中尽可能多的层次中构建数据验证非常重要,但是这样做却非常耗时,以至于很多开发人员都会干脆忽略这个步骤 —— 这可能会导致今后大量问题的产生。但是随着最新版本的 Java 平台中引入了注释,验证变得简单得多了。在本文中,Ted Bergeron 将向您介绍如何使用 Hibernate Annotations 的 Validator 组件在 Web 应用程序中轻松构建并维护验证逻辑。

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

posted @ 2006-10-16 14:19 xzc 阅读(722) | 评论 (0)编辑 收藏
     摘要: 大多数开发人员现在还在使用if else的过程结构,曾看过jdon的banq大哥写的一篇文章,利用command,aop模式替代if else过程结构。当时还不太明白,这几天看了《重构》第一章的影片租赁案例,感触颇深。下面我来谈一谈为什么要用state pattern替代if else,替代if else有什么好处,以及给出详细代码怎么替代if else。本文参考jdon的“你还在使用if els...  阅读全文
posted @ 2006-10-16 13:46 xzc 阅读(3126) | 评论 (0)编辑 收藏
FCKeditor,作为现在功能最强大的在线HTML编辑器,网上关于他的功能介绍以及基本配置已经很多了。然而其中不少文章涉及面都比较局限。最近,笔者需要在自己项目中使用到FCKeditor,并用之于和已有的基于JSF的web应用整合。从对FCKeditor一窍不通到成功达成整合,我从网上学到了不少知识,自己也积累了不少经验,因此,也想和大家一起分析这一过程。

   1.基本配置:

      知之为知之,不知google之。关于FCKeditor的基本配置在网上自有高人指点,这里我也不多耽误大家时间。主要是谈下自己当初配置的问题:
    a.基本路径:
        注意FCKeditor的基本路径应该是/(你的web应用名称)/(放置FCKeditor文件的文件夹名)/
        我的目录结构为:
       
        因此,我的基本路径设置为:/fck/FCKeditor/

     b.文件浏览以及上传路径设置:
        我个人的参考如下:
      
FCKConfig.LinkBrowser = true ;
FCKConfig.LinkBrowserURL = FCKConfig.BasePath + 'filemanager/browser/default/browser.html?Connector=connectors/jsp/connector' ;
FCKConfig.LinkBrowserWindowWidth    = FCKConfig.ScreenWidth * 0.7 ;        // 70%
FCKConfig.LinkBrowserWindowHeight    = FCKConfig.ScreenHeight * 0.7 ;    // 70%

FCKConfig.ImageBrowser = true ;
FCKConfig.ImageBrowserURL = FCKConfig.BasePath + 'filemanager/browser/default/browser.html?Type=Image
&Connector=connectors/jsp/connector';
FCKConfig.ImageBrowserWindowWidth  = FCKConfig.ScreenWidth * 0.7 ;    // 70% ;
FCKConfig.ImageBrowserWindowHeight = FCKConfig.ScreenHeight * 0.7 ;    // 70% ;

FCKConfig.FlashBrowser = true ;
FCKConfig.FlashBrowserURL = FCKConfig.BasePath + 'filemanager/browser/default/browser.html?Type=Flash
&Connector=connectors/jsp/connector' ;
FCKConfig.FlashBrowserWindowWidth  = FCKConfig.ScreenWidth * 0.7 ;    //70% ;
FCKConfig.FlashBrowserWindowHeight = FCKConfig.ScreenHeight * 0.7 ;    //70% ;

FCKConfig.LinkUpload = true ;
FCKConfig.LinkUploadURL = FCKConfig.BasePath + 'filemanager/upload/simpleuploader?Type=File' ;
FCKConfig.LinkUploadAllowedExtensions    = "" ;            // empty for all
FCKConfig.LinkUploadDeniedExtensions    = ".(php|php3|php5|phtml|asp|aspx|ascx|jsp|cfm|cfc|pl|bat|exe|dll|reg|cgi)$" ;    // empty for no one

FCKConfig.ImageUpload = true ;
FCKConfig.ImageUploadURL =FCKConfig.BasePath + 'filemanager/upload/simpleuploader?Type=Image' ;
FCKConfig.ImageUploadAllowedExtensions    = ".(jpg|gif|jpeg|png)$" ;        // empty for all
FCKConfig.ImageUploadDeniedExtensions    = "" ;                            // empty for no one

FCKConfig.FlashUpload = true ;
FCKConfig.FlashUploadURL = FCKConfig.BasePath + 'filemanager/upload/simpleuploader?Type=Flash' ;
FCKConfig.FlashUploadAllowedExtensions    = ".(swf|fla)$" ;        // empty for all
FCKConfig.FlashUploadDeniedExtensions    = "" ;                    // empty for no one


2.与JSF整合。

  FCKeditor与JSF整合的最大问题,在于需要从JSF环境中相应Bean读取值赋予给FCKeditor,同时从FCKeditor里面读取结果赋予给相应的数据持有Bean。由于这一过程在传统的JSF标签中是通过值绑定有框架自动完成,因此这里需要我们手动来实现这一过程。
  以下要展示的demo由两部分组成:
   form.jsp显示编辑内容,点击其submit按钮跳转至test.jsp,test.jsp调用FCKeditor对form.jsp中显示的已有内容进行显示和编辑。编辑完后点击submit,页面将重新跳转到form.jsp,显示修改后的内容。
  其实,这就是一个很基本的编辑功能,在论坛和blog中都可以看到它的身影。
   而ContextBean用于持有待编辑的内容,它的scope是session范围。ContextServlet用于读取FCKeditor修改后的内容,并赋予ContextBean中。

    首先来看form.jsp
<body>  
        
<f:view>
            
<h:form>
                
<pre>
                
<h:outputText value="#{td.name}" escape="false"></h:outputText>
                
</pre>
                                
<br/>
                
<h:commandButton value="submit" action="#{td.submit}"></h:commandButton>
            
</h:form>
        
</f:view>
    
</body>

    这个页面很简单,其中td对应的就是ContextBean,ContextBean.name用于保存编辑内容

package com.dyerac;

public class ContextBean {
    
private String name;

    
public String getName() {
        
return name;
    }


    
public void setName(String name) {
        
this.name = name;
    }


    
public String submit() {
        
        
return "edit";
    }

}


下面来看test.jsp
 用过FCKeditor的都知道,FCKeditor可以通过三种方式来调用:
 script, jsp 标签以及java代码。
这里,为了方便从ContextBean中读取name属性内容作为其初始值,我使用了第三种方法
其中FCKeditor fck=new FCKeditor(request,"EditorDefault");初始化FCKeditor,第二个参数即为该FCKeditor实例的id,当提交后FCKeditor内的内容将以该参数为变量名保存在request中。
比如,这里我选择了EditorDefault,所以日后读取FCKeditor内容时,可以通过以下语句:
request.getParameter("EditorDefault")

<body>
        
<form action="/fck/servlet/ContextServlet" method="post" target="_blank">
        
<%FCKeditor fck=new FCKeditor(request,"EditorDefault");
          FacesContext fcg
=FacesContext.getCurrentInstance();
          ContextBean td
=(ContextBean)fcg.getApplication().getVariableResolver().resolveVariable(fcg,"td");
          fck.setBasePath(
"/fck/FCKeditor/");
          fck.setValue(td.getName());
          fck.setHeight(
new Integer(600).toString());
          out.print(fck.create());
         
%>
         
<input type="submit" value="Submit">
    
</body>

 修改后的结果以post方式提交给/fck/servlet/ContextServlet,该url对应的即为ContextServlet。
ContextServlet负责读取FCKeditor里的内容,并赋予给session中的ContextBean。doPost()方法用于实现该功能

public void doPost(HttpServletRequest request, HttpServletResponse response)
            
throws ServletException, IOException {
        FacesContext fcg 
= getFacesContext(request,response);
        ContextBean td 
= (ContextBean) fcg.getApplication()
                .getVariableResolver().resolveVariable(fcg, 
"td");
        String name
=new String(request.getParameter("EditorDefault").getBytes("ISO-8859-1"),"UTF-8");
        td.setName(name);
        RequestDispatcher rd
=getServletContext().getRequestDispatcher("/form.faces");
        rd.forward(request, response);
    }

需要注意两个问题,
其一:FCKeditor内的中文信息读取是可能出现乱码,需要额外的处理:
   String name=new String(request.getParameter("EditorDefault").getBytes("ISO-8859-1"),"UTF-8");
其二:由于servlet处于FacesContext范围外,因此不能通过FacesContext.getCurrentInstance()来得到当前FacesContext,因此ContextServlet定义了自己的方法用于获取FacesContext:

//     You need an inner class to be able to call FacesContext.setCurrentInstance
//     since it's a protected method
    private abstract static class InnerFacesContext extends FacesContext
    
{
      
protected static void setFacesContextAsCurrentInstance(FacesContext facesContext) {
        FacesContext.setCurrentInstance(facesContext);
      }

    }


    
private FacesContext getFacesContext(ServletRequest request, ServletResponse response) {
      
// Try to get it first
      FacesContext facesContext = FacesContext.getCurrentInstance();
      
if (facesContext != nullreturn facesContext;

      FacesContextFactory contextFactory 
= (FacesContextFactory)FactoryFinder.getFactory(FactoryFinder.FACES_CONTEXT_FACTORY);
      LifecycleFactory lifecycleFactory 
= (LifecycleFactory)FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
      Lifecycle lifecycle 
= lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);

      
// Either set a private member servletContext = filterConfig.getServletContext();
      
// in you filter init() method or set it here like this:
      
// ServletContext servletContext = ((HttpServletRequest)request).getSession().getServletContext();
      
// Note that the above line would fail if you are using any other protocol than http
      ServletContext servletContext = ((HttpServletRequest)request).getSession().getServletContext();

      
// Doesn't set this instance as the current instance of FacesContext.getCurrentInstance
      facesContext = contextFactory.getFacesContext(servletContext, request, response, lifecycle);

      
// Set using our inner class
      InnerFacesContext.setFacesContextAsCurrentInstance(facesContext);

      
// set a new viewRoot, otherwise context.getViewRoot returns null
      
//UIViewRoot view = facesContext.getApplication().getViewHandler().createView(facesContext, "yourOwnID");
      
//facesContext.setViewRoot(view);

      
return facesContext;
    }
 

   ContextServlet处理完了FCKeditor内容后,将跳转到form.jsp。
这样一个简单的编辑功能就完成了。

3.遗留问题:

   我在上传文件时还是会出现中文乱码的问题,按照其他人说的那样把网页的charset=utf-8改成gb2312一样会有问题,还请各位高手赐教^_^


另外,关于整个demo的源代码如果大家需要,可以留言给我,我用邮箱发给您。不足之处,还请各位多多指点

posted @ 2006-10-14 17:13 xzc 阅读(410) | 评论 (0)编辑 收藏
关键字:       

开发环境:
Tomcat5.5 Eclipse3.2

FCKeditor 版本 FCKeditor_2.3.1 FCKeditor.Java 2.3

下载地址: http://www.fckeditor.net/download/default.html

开始:
新建工程,名称为 fekeditor
解压 FCKeditor_2.3.1 包中的 edit 文件夹到项目中的 WebContent 目录

解压 FCKeditor_2.3.1 包中的 fckconfig.js、fckeditor.js、fckstyles.xml、fcktemplates.xml 文件夹到项目中的 WebContent 目录

解压 FCKeditor-2.3.zip 包中的 \web\WEB-INF\lib 下的两个 jar 文件到项目的 WebContent\WEB-INF\lib 目录

解压 FCKeditor-2.3.zip 包中的 \src 下的 FCKeditor.tld 文件到项目的 WebContent\WEB-INF 目录

删除 WebContent\edit 目录下的 _source 文件夹

修改 web.xml 文件,加入以下内容

代码
																<
																servlet
																>
																
< servlet - name > Connector </ servlet-name>
<servlet-class>
com.fredck.FCKeditor.connector.ConnectorServlet
<
/ servlet - class >
< init - param >
< param - name > baseDir </ param-name>
<!-- 此为文件浏览路径 -->
<param-value>
/ UserFiles /</ param-value>
<
/ init - param >
< init - param >
< param - name > debug </ param-name>
<param-value>true<
/ param - value >
</ init-param>
<load-on-startup>1<
/ load - on - startup >
</ servlet> <servlet>
<servlet-name>SimpleUploader<
/ servlet - name >
< servlet - class >
com . fredck . FCKeditor . uploader . SimpleUploaderServlet
</ servlet-class>
<init-param>
<param-name>baseDir<
/ param - name >
<!-- 此为文件上传路径,需要在WebContent 目录下新建 UserFiles 文件夹 -->
<!-- 根据文件的类型还需要新建相关的文件夹 Image、Flash-->
<param-value>/UserFiles/</param-value>
<
/init-param>
<init-param>
<param-name>debug</param-name>
<param-value>true<
/param-value>
</init-param>
<init-param>
<!-- 此参数为是否开启上传功能 -->
<param-name>enabled<
/param-name>
<param-value>false</param-value>
<
/init-param>
<init-param>
<param-name>AllowedExtensionsFile</param-name>
<param-value><
/param-value>
</init-param>
<init-param> <!-- 此参数为文件过滤,以下的文件类型都不可以上传 -->
<param-name>DeniedExtensionsFile<
/param-name>
<param-value>
php|php3|php5|phtml|asp|aspx|ascx|jsp|cfm|cfc|pl|bat|exe|dll|reg|cgi
</param-value>
<
/init-param>
<init-param>
<param-name>AllowedExtensionsImage</param-name>
<param-value>jpg|gif|jpeg|png|bmp<
/param-value>
</init-param>
<init-param>
<param-name>DeniedExtensionsImage<
/param-name>
<param-value></param-value>
<
/init-param>
<init-param>
<param-name>AllowedExtensionsFlash</param-name>
<param-value>swf|fla<
/param-value>
</init-param>
<init-param>
<param-name>DeniedExtensionsFlash<
/param-name>
<param-value></param-value>
<
/init-param>
<load-on-startup>1</load-on-startup>
<
/servlet><servlet-mapping>
<servlet-name>Connector</servlet-name>
<url-pattern>
/editor/filemanager/browser/default/connectors/jsp/connector
</url-pattern>
<
/servlet-mapping><servlet-mapping>
<servlet-name>SimpleUploader</servlet-name>
<url-pattern>
/editor/filemanager/upload/simpleuploader
</url-pattern>
<
/servlet-mapping>

新建一个提交页 jsp1.jsp 文件和一个接收页 jsp2.jsp 文件

jsp1.jsp 代码如下:

代码
<%@ page contentType = "text/html;charset=UTF-8" language = "java" %>

<%
@tagliburi="/WEB-INF/FCKeditor.tld"prefix="fck"%><html>
<head>
<title>Test</title>
<
/head><body>
<FORMaction="jsp2.jsp">
<fck:editorid="testfck"basePath="/fekeditor/"
height="100%"
skinPath="/fekeditor/editor/skins/default/"
toolbarSet="Default"
imageBrowserURL="/fekeditor/editor/filemanager/browser/default/browser.html?Type=Image&Connector=connectors/jsp/connector"
linkBrowserURL="/fekeditor/editor/filemanager/browser/default/browser.html?Connector=connectors/jsp/connector"
flashBrowserURL="/fekeditor/editor/filemanager/browser/default/browser.html?Type=Flash&Connector=connectors/jsp/connector"
imageUploadURL="/fekeditor/editor/filemanager/upload/simpleuploader?Type=Image"
linkUploadURL="/fekeditor/editor/filemanager/upload/simpleuploader?Type=File"
flashUploadURL="/fekeditor/editor/filemanager/upload/simpleuploader?Type=Flash">
</fck:editor>
<input type="submit"
/>
</FORM>
<
/body>
</html>jsp2.jsp 代码如下:<html>
<head>
<title>TEST<
/title>
</head> <body>
<%=request.getParameter( "testfck" )%>
<
/body>
</html>

在 WebContent 目录下新建 UserFiles 文件夹,在此文件夹下新建 File,Image,Flash 三个文件夹。

ok现在运行一下看看吧!

posted @ 2006-10-14 17:08 xzc 阅读(487) | 评论 (0)编辑 收藏

试用了一下FCKeditor,根据网上的文章小结一下:
1.下载
FCKeditor.java 2.3 (FCKeditot for java)
FCKeditor 2.2 (FCKeditor基本文件)

2.建立项目:tomcat/webapps/TestFCKeditor.

3.将FCKeditor2.2解压缩,将整个目录FCKeditor复制到项目的根目录下,
目录结构为:tomcat/webapps/TestFCKeditor/FCKeditor
然后将FCKeditor-2.3.zip(java)压缩包中\web\WEB-INF\lib\目录下的两个jar文件拷到项目的\WEB-INF\lib\目录下。把其中的src目录下的FCKeditor.tld文件copy到TestFCKedit/FCKeitor/WEB-INF/下

4.将FCKeditor-2.3.zip压缩包中\web\WEB-INF\目录下的web.xml文件合并到项目的\WEB-INF\目录下的web.xml文件中。

5. 修改合并后的web.xml文件,将名为SimpleUploader的Servlet的enabled参数值改为true,
以允许上传功能,Connector Servlet的baseDir参数值用于设置上传文件存放的位置。
添加标签定义:
<taglib>
<taglib-uri>/TestFCKeditor</taglib-uri>
<taglib-location>/WEB-INF/FCKeditor.tld</taglib-location>
</taglib>

运行图:

6. 上面文件中两个servlet的映射分别为:/editor/filemanager/browser/default/connectors/jsp/connector
和/editor/filemanager/upload/simpleuploader,需要在两个映射前面加上/FCKeditor,
即改为/FCKeditor/editor/filemanager/browser/default/connectors/jsp/connector和
/FCKeditor/editor/filemanager/upload/simpleuploader。

7.进入skin文件夹,如果你想使用fckeditor默认的这种奶黄色,
那就把除了default文件夹外的另两个文件夹直接删除.

8.删除/FCKeditor/目录下除fckconfig.js, fckeditor.js, fckstyles.xml, fcktemplates.xml四个文件以外的所有文件
删除目录/editor/_source,
删除/editor/filemanager/browser/default/connectors/下的所有文件
删除/editor/filemanager/upload/下的所有文件
删除/editor/lang/下的除了fcklanguagemanager.js, en.js, zh.js, zh-cn.js四个文件的所有文件

9.打开/FCKeditor/fckconfig.js
修改 FCKConfig.DefaultLanguage = 'zh-cn' ;
把FCKConfig.LinkBrowserURL等的值替换成以下内容:
FCKConfig.LinkBrowserURL
= FCKConfig.BasePath + "filemanager/browser/default/browser.html?Connector=connectors/jsp/connector" ;

FCKConfig.ImageBrowserURL
= FCKConfig.BasePath + "filemanager/browser/default/browser.html?Type=Image&Connector=connectors/jsp/connector" ;

FCKConfig.FlashBrowserURL
= FCKConfig.BasePath + "filemanager/browser/default/browser.html?Type=Flash&Connector=connectors/jsp/connector" ;


FCKConfig.LinkUploadURL = FCKConfig.BasePath + 'filemanager/upload/simpleuploader?Type=File' ;
FCKConfig.FlashUploadURL = FCKConfig.BasePath + 'filemanager/upload/simpleuploader?Type=Flash' ;
FCKConfig.ImageUploadURL = FCKConfig.BasePath + 'filemanager/upload/simpleuploader?Type=Image' ;

10.其它
fckconfig.js总配置文件,可用记录本打开,修改后将文件存为utf-8 编码格式。找到:

FCKConfig.TabSpaces = 0 ; 改为FCKConfig.TabSpaces = 1 ; 即在编辑器域内可以使用Tab键。

如果你的编辑器还用在网站前台的话,比如说用于留言本或是日记回复时,那就不得不考虑安全了,
在前台千万不要使用Default的toolbar,要么自定义一下功能,要么就用系统已经定义好的Basic,
也就是基本的toolbar,找到:
FCKConfig.ToolbarSets["Basic"] = [
['Bold','Italic','-','OrderedList','UnorderedList','-',/*'Link',*/'Unlink','-','Style','FontSize','TextColor','BGColor','-',
'Smiley','SpecialChar','Replace','Preview'] ] ;
这是改过的Basic,把图像功能去掉,把添加链接功能去掉,因为图像和链接和flash和图像按钮添加功能都能让前台
页直接访问和上传文件, fckeditor还支持编辑域内的鼠标右键功能。

FCKConfig.ContextMenu = ['Generic',/*'Link',*/'Anchor',/*'Image',*/'Flash','Select','Textarea','Checkbox','Radio','TextField','HiddenField',
/*'ImageButton',*/'Button','BulletedList','NumberedList','TableCell','Table','Form'] ;

这也是改过的把鼠标右键的“链接、图像,FLASH,图像按钮”功能都去掉。

  找到: FCKConfig.FontNames = 'Arial;Comic Sans MS;Courier New;Tahoma;Times New Roman;Verdana' ;
加上几种我们常用的字体
FCKConfig.FontNames
= '宋体;黑体;隶书;楷体_GB2312;Arial;Comic Sans MS;Courier New;Tahoma;Times New Roman;Verdana' ;

7.添加文件 /TestFCKeditor/test.jsp:
<%@ page language="java" import="com.fredck.FCKeditor.*" %>
<%@ taglib uri="/TestFCKeditor" prefix="FCK" %>
<script type="text/javascript" src="/TestFCKeditor/FCKeditor/fckeditor.js"></script>

<%--
三种方法调用FCKeditor
1.FCKeditor自定义标签 (必须加头文件 <%@ taglib uri="/TestFCKeditor" prefix="FCK" %> )
2.script脚本语言调用 (必须引用 脚本文件 <script type="text/javascript" src="/TestFCKeditor/FCKeditor/fckeditor.js"></script> )
3.FCKeditor API 调用 (必须加头文件 <%@ page language="java" import="com.fredck.FCKeditor.*" %> )
--%>

<%--
<form action="show.jsp" method="post" target="_blank">
<FCK:editor id="content" basePath="/TestFCKeditor/FCKeditor/"
width="700"
height="500"
skinPath="/TestFCKeditor/FCKeditor/editor/skins/silver/"
toolbarSet = "Default"
>
input
</FCK:editor>
<input type="submit" value="Submit">
</form>
--%>

<form action="show.jsp" method="post" target="_blank">
<table border="0" width="700"><tr><td>
<textarea id="content" name="content" style="WIDTH: 100%; HEIGHT: 400px">input</textarea>
<script type="text/javascript">
var oFCKeditor = new FCKeditor('content') ;
oFCKeditor.BasePath = "/TestFCKeditor/FCKeditor/" ;
oFCKeditor.Height = 400;
oFCKeditor.ToolbarSet = "Default" ;
oFCKeditor.ReplaceTextarea();
</script>
<input type="submit" value="Submit">
</td></tr></table>
</form>

<%--
<form action="show.jsp" method="post" target="_blank">
<%
FCKeditor oFCKeditor ;
oFCKeditor = new FCKeditor( request, "content" ) ;
oFCKeditor.setBasePath( "/TestFCKeditor/FCKeditor/" ) ;
oFCKeditor.setValue( "input" );
out.println( oFCKeditor.create() ) ;
%>
<br>
<input type="submit" value="Submit">
</form>
--%>

添加文件/TestFCKeditor/show.jsp:
<%
String content = request.getParameter("content");
out.print(content);
%>

8.浏览http://localhost:8080/TestFCKeditor/test.jsp
ok!

posted @ 2006-10-11 17:19 xzc 阅读(211) | 评论 (0)编辑 收藏

最近写书,写到JNDI,到处查资料,发现所有的中文资料都对JNDI解释一通,配置代码也是copy的,调了半天也没调通,最后到SUN的网站参考了一下他的JNDI tutorial,终于基本上彻底明白了

和多数java服务一样,SUN对JNDI也只提供接口,使用JNDI只需要用到JNDI接口而不必关心具体实现:

private static Object jndiLookup() throws Exception {
  InitialContext ctx = new InitialContext();
  return ctx.lookup("java:comp/env/systemStartTime");
}

上述代码在J2EE服务器环境下工作得很好,但是在main()中就会报一个NoInitialContextException,许多文章会说你创建InitialContext的时候还要传一个Hashtable或者Properties,像这样:

Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
env.put(Context.PROVIDER_URL,"t3://localhost:7001");
InitialContext ctx = new InitialContext(env);

这个在WebLogic环境下是对的,但是换到JBoss呢?再用JBoss的例子?

其实之所以有NoInitialContextException是因为无法从System.properties中获得必要的JNDI参数,在服务器环境下,服务器启动时就把这些参数放到System.properties中了,于是直接new InitialContext()就搞定了,不要搞env那么麻烦,搞了env你的代码还无法移植,弄不好管理员设置服务器用的不是标准端口还照样抛异常。

但是在单机环境下,可没有JNDI服务在运行,那就手动启动一个JNDI服务。我在JDK 5的rt.jar中一共找到了4种SUN自带的JNDI实现:

LDAP,CORBA,RMI,DNS。

这4种JNDI要正常运行还需要底层的相应服务。一般我们没有LDAP或CORBA服务器,也就无法启动这两种JNDI服务,DNS用于查域名的,以后再研究,唯一可以在main()中启动的就是基于RMI的JNDI服务。

现在我们就在main()中启动基于RMI的JNDI服务并且绑一个Date对象到JNDI上:

LocateRegistry.createRegistry(1099);
System.setProperty(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
System.setProperty(Context.PROVIDER_URL, "rmi://localhost:1099");
InitialContext ctx = new InitialContext();
class RemoteDate extends Date implements Remote {};
ctx.bind("java:comp/env/systemStartTime", new RemoteDate());
ctx.close();

注意,我直接把JNDI的相关参数放入了System.properties中,这样,后面的代码如果要查JNDI,直接new InitialContext()就可以了,否则,你又得写Hashtable env = ...

在RMI中绑JNDI的限制是,绑定的对象必须是Remote类型,所以就自己扩展一个。

其实JNDI还有两个Context.SECURITY_PRINCIPAL和Context.SECURITY_CREDENTIAL,如果访问JNDI需要用户名和口令,这两个也要提供,不过一般用不上。

在后面的代码中查询就简单了:

InitialContext ctx = new InitialContext();
Date startTime = (Date) ctx.lookup("java:comp/env/systemStartTime");

在SUN的JNDI tutorial中的例子用的com.sun.jndi.fscontext.RefFSContextFactory类,但是我死活在JDK 5中没有找到这个类,也就是NoClassDefFoundError,他也不说用的哪个扩展包,我也懒得找了。 

posted @ 2006-10-11 14:55 xzc 阅读(1860) | 评论 (0)编辑 收藏

摘要:

本文阐述了怎么使用DBMS存储过程。我阐述了使用存储过程的基本的和高级特性,比如返回ResultSet。本文假设你对DBMS和JDBC已经非常熟悉,也假设你能够毫无障碍地阅读其它语言写成的代码(即不是Java的语言),但是,并不要求你有任何存储过程的编程经历。
本文阐述了怎么使用DBMS存储过程。我阐述了使用存储过程的基本的和高级特性,比如返回ResultSet。本文假设你对DBMS和JDBC已经非常熟悉,也假设你能够毫无障碍地阅读其它语言写成的代码(即不是Java的语言),但是,并不要求你有任何存储过程的编程经历。

存储过程是指保存在数据库并在数据库端执行的程序。你可以使用特殊的语法在Java类中调用存储过程。在调用时,存储过程的名称及指定的参数通过JDBC连接发送给DBMS,执行存储过程并通过连接(如果有)返回结果。
使用存储过程拥有和使用基于EJB或CORBA这样的应用服务器一样的好处。区别是存储过程可以从很多流行的DBMS中免费使用,而应用服务器大都非常昂贵。这并不只是许可证费用的问题。使用应用服务器所需要花费的管理、编写代码的费用,以及客户程序所增加的复杂性,都可以通过DBMS中的存储过程所整个地替代。

你可以使用Java,Python,Perl或C编写存储过程,但是通常使用你的DBMS所指定的特定语言。Oracle使用PL/SQL,PostgreSQL使用pl/pgsql,DB2使用Procedural SQL。这些语言都非常相似。在它们之间移植存储过程并不比在Sun的EJB规范不同实现版本之间移植Session Bean困难。并且,存储过程是为嵌入SQL所设计,这使得它们比Java或C等语言更加友好地方式表达数据库的机制。

因为存储过程运行在DBMS自身,这可以帮助减少应用程序中的等待时间。不是在Java代码中执行4个或5个SQL语句,而只需要在服务器端执行1个存储过程。网络上的数据往返次数的减少可以戏剧性地优化性能。

使用存储过程

简单的老的JDBC通过CallableStatement类支持存储过程的调用。该类实际上是PreparedStatement的一个子类。假设我们有一个poets数据库。数据库中有一个设置诗人逝世年龄的存储过程。下面是对老酒鬼Dylan Thomas(old soak Dylan Thomas,不指定是否有关典故、文化,请批评指正。译注)进行调用的详细代码:

try{ 
int age = 39;

String poetName = "dylan thomas";

CallableStatement proc = connection.prepareCall("{ call set_death_age(?, ?) }");

proc.setString(1, poetName);

proc.setInt(2, age);

cs.execute();

}catch (SQLException e){ // ....}


传给prepareCall方法的字串是存储过程调用的书写规范。它指定了存储过程的名称,?代表了你需要指定的参数。
和JDBC集成是存储过程的一个很大的便利:为了从应用中调用存储过程,不需要存根(stub)类或者配置文件,除了你的DBMS的JDBC驱动程序外什么也不需要。

当这段代码执行时,数据库的存储过程就被调用。我们没有去获取结果,因为该存储过程并不返回结果。执行成功或失败将通过例外得知。失败可能意味着调用存储过程时的失败(比如提供的一个参数的类型不正确),或者一个应用程序的失败(比如抛出一个例外指示在poets数据库中并不存在“Dylan Thomas”)

结合SQL操作与存储过程

映射Java对象到SQL表中的行相当简单,但是通常需要执行几个SQL语句;可能是一个SELECT查找ID,然后一个INSERT插入指定ID的数据。在高度规格化(符合更高的范式,译注)的数据库模式中,可能需要多个表的更新,因此需要更多的语句。Java代码会很快地膨胀,每一个语句的网络开销也迅速增加。
将这些SQL语句转移到一个存储过程中将大大简化代码,仅涉及一次网络调用。所有关联的SQL操作都可以在数据库内部发生。并且,存储过程语言,例如PL/SQL,允许使用SQL语法,这比Java代码更加自然。下面是我们早期的存储过程,使用Oracle的PL/SQL语言编写:

create procedure set_death_age(poet VARCHAR2, poet_age NUMBER) 

poet_id NUMBER;

begin SELECT id INTO poet_id FROM poets WHERE name = poet;

INSERT INTO deaths (mort_id, age) VALUES (poet_id, poet_age);

end set_death_age;


很独特?不。我打赌你一定期待看到一个poets表上的UPDATE。这也暗示了使用存储过程实现是多么容易的一件事情。set_death_age几乎可以肯定是一个很烂的实现。我们应该在poets表中添加一列来存储逝世年龄。Java代码中并不关心数据库模式是怎么实现的,因为它仅调用存储过程。我们以后可以改变数据库模式以提高性能,但是我们不必修改我们代码。

下面是调用上面存储过程的Java代码:

public static void setDeathAge(Poet dyingBard, int age) throws SQLException{ 

Connection con = null;

CallableStatement proc = null;

try {

con = connectionPool.getConnection();

proc = con.prepareCall("{ call set_death_age(?, ?) }");

proc.setString(1, dyingBard.getName());

proc.setInt(2, age);

proc.execute();

}

finally {

try { proc.close(); }

catch (SQLException e) {}

con.close();

}

}


为了确保可维护性,建议使用像这儿这样的static方法。这也使得调用存储过程的代码集中在一个简单的模版代码中。如果你用到许多存储过程,就会发现仅需要拷贝、粘贴就可以创建新的方法。因为代码的模版化,甚至也可以通过脚本自动生产调用存储过程的代码。

Functions

存储过程可以有返回值,所以CallableStatement类有类似getResultSet这样的方法来获取返回值。当存储过程返回一个值时,你必须使用registerOutParameter方法告诉JDBC驱动器该值的SQL类型是什么。你也必须调整存储过程调用来指示该过程返回一个值。

下面接着上面的例子。这次我们查询Dylan Thomas逝世时的年龄。这次的存储过程使用PostgreSQL的pl/pgsql:

create function snuffed_it_when (VARCHAR) returns integer ''declare 

poet_id NUMBER;

poet_age NUMBER;

begin

--first get the id associated with the poet.

SELECT id INTO poet_id FROM poets WHERE name = $1;

--get and return the age.

SELECT age INTO poet_age FROM deaths WHERE mort_id = poet_id;

return age;

end;'' language ''pl/pgsql'';


另外,注意pl/pgsql参数名通过Unix和DOS脚本的$n语法引用。同时,也注意嵌入的注释,这是和Java代码相比的另一个优越性。在Java中写这样的注释当然是可以的,但是看起来很凌乱,并且和SQL语句脱节,必须嵌入到Java String中。

下面是调用这个存储过程的Java代码:

connection.setAutoCommit(false); 

CallableStatement proc = connection.prepareCall("{ ? = call snuffed_it_when(?) }");

proc.registerOutParameter(1, Types.INTEGER);

proc.setString(2, poetName);

cs.execute();

int age = proc.getInt(2);


如果指定了错误的返回值类型会怎样?那么,当调用存储过程时将抛出一个RuntimeException,正如你在ResultSet操作中使用了一个错误的类型所碰到的一样。

复杂的返回值

关于存储过程的知识,很多人好像就熟悉我们所讨论的这些。如果这是存储过程的全部功能,那么存储过程就不是其它远程执行机制的替换方案了。存储过程的功能比这强大得多。
当你执行一个SQL查询时,DBMS创建一个叫做cursor(游标)的数据库对象,用于在返回结果中迭代每一行。ResultSet是当前时间点的游标的一个表示。这就是为什么没有缓存或者特定数据库的支持,你只能在ResultSet中向前移动。

某些DBMS允许从存储过程中返回游标的一个引用。JDBC并不支持这个功能,但是Oracle、PostgreSQL和DB2的JDBC驱动器都支持在ResultSet上打开到游标的指针(pointer)。

设想列出所有没有活到退休年龄的诗人,下面是完成这个功能的存储过程,返回一个打开的游标,同样也使用PostgreSQL的pl/pgsql语言:

create procedure list_early_deaths () return refcursor as ''declare 

toesup refcursor;

begin

open toesup for SELECT poets.name, deaths.age FROM poets, deaths -- all entries in deaths are for poets. -- but the table might become generic.

WHERE poets.id = deaths.mort_id AND deaths.age < 60;

return toesup;

end;'' language ''plpgsql'';


下面是调用该存储过程的Java方法,将结果输出到PrintWriter:
PrintWriter: 

static void sendEarlyDeaths(PrintWriter out){

Connection con = null;

CallableStatement toesUp = null;

try {

con = ConnectionPool.getConnection();

// PostgreSQL needs a transaction to do this... con.

setAutoCommit(false); // Setup the call.

CallableStatement toesUp = connection.prepareCall("{ ? = call list_early_deaths () }");

toesUp.registerOutParameter(1, Types.OTHER);

toesUp.execute();

ResultSet rs = (ResultSet) toesUp.getObject(1);

while (rs.next()) {

String name = rs.getString(1);

int age = rs.getInt(2);

out.println(name + " was " + age + " years old.");

}

rs.close();

}

catch (SQLException e) { // We should protect these calls. toesUp.close(); con.close();

}

}


因为JDBC并不直接支持从存储过程中返回游标,我们使用Types.OTHER来指示存储过程的返回类型,然后调用getObject()方法并对返回值进行强制类型转换。

这个调用存储过程的Java方法是mapping的一个好例子。Mapping是对一个集上的操作进行抽象的方法。不是在这个过程上返回一个集,我们可以把操作传送进去执行。本例中,操作就是把ResultSet打印到一个输出流。这是一个值得举例的很常用的例子,下面是调用同一个存储过程的另外一个方法实现:

public class ProcessPoetDeaths{ 

public abstract void sendDeath(String name, int age);

}

static void mapEarlyDeaths(ProcessPoetDeaths mapper){

Connection con = null;

CallableStatement toesUp = null;

try {

con = ConnectionPool.getConnection();

con.setAutoCommit(false);

CallableStatement toesUp = connection.prepareCall("{ ? = call list_early_deaths () }");

toesUp.registerOutParameter(1, Types.OTHER);

toesUp.execute();

ResultSet rs = (ResultSet) toesUp.getObject(1);

while (rs.next()) {

String name = rs.getString(1);

int age = rs.getInt(2);

mapper.sendDeath(name, age);

}

rs.close();

} catch (SQLException e) { // We should protect these calls. toesUp.close();

con.close();

}

}


这允许在ResultSet数据上执行任意的处理,而不需要改变或者复制获取ResultSet的方法:

static void sendEarlyDeaths(final PrintWriter out){ 

ProcessPoetDeaths myMapper = new ProcessPoetDeaths() {

public void sendDeath(String name, int age) {

out.println(name + " was " + age + " years old.");

}

};

mapEarlyDeaths(myMapper);

}


这个方法使用ProcessPoetDeaths的一个匿名实例调用mapEarlyDeaths。该实例拥有sendDeath方法的一个实现,和我们上面的例子一样的方式把结果写入到输出流。当然,这个技巧并不是存储过程特有的,但是和存储过程中返回的ResultSet结合使用,是一个非常强大的工具。

结论

存储过程可以帮助你在代码中分离逻辑,这基本上总是有益的。这个分离的好处有:
• 快速创建应用,使用和应用一起改变和改善的数据库模式。
• 数据库模式可以在以后改变而不影响Java对象,当我们完成应用后,可以重新设计更好的模式。
• 存储过程通过更好的SQL嵌入使得复杂的SQL更容易理解。
• 编写存储过程比在Java中编写嵌入的SQL拥有更好的工具--大部分编辑器都提供语法高亮!
• 存储过程可以在任何SQL命令行中测试,这使得调试更加容易。

并不是所有的数据库都支持存储过程,但是存在许多很棒的实现,包括免费/开源的和非免费的,所以移植并不是一个问题。Oracle、PostgreSQL和DB2都有类似的存储过程语言,并且有在线的社区很好地支持。
存储过程工具很多,有像TOAD或TORA这样的编辑器、调试器和IDE,提供了编写、维护PL/SQL或pl/pgsql的强大的环境。

存储过程确实增加了你的代码的开销,但是它们和大多数的应用服务器相比,开销小得多。
posted @ 2006-10-11 14:22 xzc 阅读(215) | 评论 (0)编辑 收藏

近日学习了一下AJAX,照做了几个例子,感觉比较新奇。

第一个就是自动完成的功能即Autocomplete,具体的例子可以在这里看:http://www.b2c-battery.co.uk

在Search框内输入一个产品型号,就可以看见效果了。

这里用到了一个开源的代码:AutoAssist ,有兴趣的可以看一下。

以下为代码片断:

index.htm

<script type="text/javascript" src="javascripts/prototype.js"></script>
<script type="text/javascript" src="javascripts/autoassist.js"></script>
<link rel="stylesheet" type="text/css" href="styles/autoassist.css"/>

<div>

<input type="text" name="keyword" id="keyword"/>
<script type="text/javascript">
Event.observe(window, "load", function() {
 var aa = new AutoAssist("keyword", function() {
  return "forCSV.php?q=" + this.txtBox.value;
 });
});

</script>
</div>

不知道为什么不能用keywords做文本框的名字,我试了很久,后来还是用keyword,搞得还要修改原代码。

forCSV.php

<?php
  $keyword = $_GET['q'];
  $count = 0;
  $handle = fopen("products.csv", "r");
  while (($data = fgetcsv($handle, 1000)) !== FALSE) {
    if (preg_match("/$keyword/i", $data[0])) {
      if ($count++ > 10) { break; }
?>
      <div onSelect="this.txtBox.value='<?php echo $data[0]; ?>';">
        <?php echo $data[0]; ?>
      </div>
<?php
    }
  }
  fclose($handle);
  if ($count == 0) {
?>
  : (, nothing found.
<?php
  }
?>

原来的例子中的CSV文件是根据\t来分隔的,我们也可以用空格或其它的来分隔,这取决于你的数据结构。

当然你也可以不读文件,改从数据库里读资料,就不再废话了。

效果图如下:

 



Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=635858

[点击此处收藏本文]   jxyuhua发表于 2006年03月23日 12:08:00

posted @ 2006-10-08 14:12 xzc 阅读(388) | 评论 (1)编辑 收藏
仅列出标题
共32页: First 上一页 24 25 26 27 28 29 30 31 32 下一页