现在的位置: 首页 > 综合 > 正文

在 Java Web 开发框架中创建VoiceXML页面–在Java中创建VoiceXML库

2013年05月03日 ⁄ 综合 ⁄ 共 16364字 ⁄ 字号 评论关闭

 

java Web 开发框架中创建VoiceXML页面--在Java中创建VoiceXML库

编辑:未知 文章来源:互联网

掌握了基于 java 的 VoiceXML 应用程序的基础知识之后,您就可以开始编写更智能化的应用程序了。巧妙地利用 java™Bean 组件、servlet、JavaServer Pages(JSP)技术和普通旧式 java 对象(POJO),您就可以使应用程序开发比以往任何时候更快、更流线化。
构建一个应用程序 —— 无论是 VoiceXML 应用程序还是企业应用程序 —— 是一项非凡的任务。您要编写代码,确保满足所有需求,保持依赖性,处理构建、测试与部署……这绝不是一项简单的工作。编写无线应用程序时,还有另外一整套的约束要去考虑:能利用的内存空间较少、目标设备(电话或 PDA)上的资源有限,必须保证代码极其简洁、最优化。更糟糕的是,对应用程序的最细微的更改都可能使之在某些设备上无法操作,因而就需要更复杂的调试和(重新)测试。

消除部分问题的最佳方法就是创建一组经过验证的工具和库,在 VoiceXML 应用程序中使用。举个例子,假设您开发了一种方法,用于处理路径常量(比如说 pages/memu.vxml),而且在多个设备上进行了测试,那么肯定不想弄乱这些能够正常工作的代码。当然,如果那些代码集成在应用程序中 —— 只是其他特定应用程序的代码行之内的一些代码行,您就会遇到一个大问题:无法很轻松地在新的应用程序中重用这些可以正常工作、经过测试的代码,即便这些程序需要的是完全相同的功能。

为了避免这种令人困扰的问题,您可以将处理路径的代码从应用程序中提取出来,将它打包成一个通用的方法或类 —— 可供任何 VoiceXML 应用程序使用,成功!您得到了一个库。这个库应会影响任何 VoiceXML 应用程序,而您立即就能够避免重新编写和重新测试能够工作的代码了。构建多个库来处理 VoiceXML 例行任务和通用任务,您的应用程序开发也会得到极大的 简化。这正是本文讨论的重点:尽可能地将一切放到库和可重用代码片段中,从而使 VoiceXML 应用程序开发变成一项更轻松的任务。

巩固基础
前几篇文章已经介绍了一些有助于大规模 VoiceXML 应用程序开发的工具和技术。但大多是从特定技术的角度来阐述的。JSP 技术提供了一种编译更少、更轻松的方法;servlet 使得读入和载入 VXML 文件更轻松;JavaBean 组件是定义和重用常量的理想方式。在讨论新工具、新技术之前,先来快速回顾一下您应已理解的内容。务必熟悉每一种编码 VXML 应用程序的方法,因为后文中介绍的许多技术都建立在这些方法的基础之上。

静态 VXML 文件
在本系列的 第一篇文章 中,您学习了如何获得静态 VXML 文件,并将其转换为 servlet。完成这一任务的最简单的方法之一就是使用一个 servlet,它直接读入一个完整的 VXML 文件,并逐行输出它。用于此目的的 servlet 如 清单 1 所示。

清单 1. 载入一个静态 VXML 文件

package com.ibm.vxml;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.*;

public class VoiceXMLServlet extends HttpServlet {

  private static final String VXML_FILENAME =
    "simple-voice_recog.xml";

  public void doGet(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException {

    String vxmlDir = getServletContext().getInitParameter("vxml-dir");

    BufferedInputStream bis = null;
    ServletOutputStream out = null;

    try {
      // Load the VXML file
      File vxml = new File(vxmlDir + "/" + VXML_FILENAME);
      FileInputStream fis = new FileInputStream(vxml);
      bis = new BufferedInputStream(fis);

      // Output the VXML file
      int readBytes = 0;
      while ((readBytes = bis.read()) != -1) {
        // output the VXML
      }
    } finally {
      if (out != null) out.close();
      if (bis != null) bis.close();
    }
  }
}
 

 添加一些选项

这个版本的 readFile() 实际上把一切都过分简单化了,但调整它很容易。您可能希望添加一个版本,接受 File 输入而不是简单的 String;也可能有一个 清单 2 中所示版本的 readFile(),用于接受 File 的版本。还可能希望添加一个接受 Writer 输入的版本,而不是 OutputStream,从而获得更多的灵活性。
 
这看起来不太像可重用组件,但考虑到重用一个 VXML 文件的特定部分的频率时,您就不会这么想了。例如,您想根据用户使用的电话类型或用户凭证更改对用户可用的提示和选项,但最后总是要反复地使用相同的 VXML 语法。在此类情况下,您可以使用一个读入 VXML 文件片段的 java 函数。清单 2 展示了一个实现此功能的方法。

清单 2. 读入 VXML 片段
   
  public static void readFile(String filename, OutputStream out)
    throws IOException {

    BufferedInputStream bis = null;

    try {
      // Load the VXML file
      File vxml = new File(filename);
      FileInputStream fis = new FileInputStream(vxml);
      bis = new BufferedInputStream(fis);

      // Output the VXML file
      int readBytes = 0;
      while ((readBytes = bis.read()) != -1) {
        // output the VXML
        out.write(readBytes);
      }
    } finally {
      if (bis != null) bis.close();
    }
  }

您可以将一个 ServletOutputStream 随文件名一起传递到这个方法中,并输出 VXML 片段。然后,若您有 VoiceXML 语法或永远不会更改的通用 VXML,可以将其留在一个 VXML 片段文件中(不含完整的 VXML 文档,而只包含一个 VXML 文档的一部分的文件),然后在需要时将其读入您的应用程序,后文将对于这种理念的一些方便的扩展加以讨论,但在编写 VoiceXML 应用程序时,您应总是使用一个简单的方法来读入静态文件。

用于查找路径的 JavaBean 组件

在本系列的 第三部分(也就是最近一篇)中,我简单介绍了使用 JavaBean 组件在 VoiceXML 应用程序中存储路径的一种方法。像 清单 3 所展示的这种 bean 就是此技术的中心可重用元素。

清单 3. 使用 JavaBean 组件来存储路径信息
   
package com.ibm.dw.voicexml;

public class LookupBean implements java.io.Serializable {

  private String identifier;
  private String filename;

  public LookupBean() { }

  public void setIdentifier(String identifier) {
    this.identifier = identifier;
    if (identifier.equals("accountInformation"))
      filename = "/profile/account-information.jsp";
    else if (identifier.equals("mainMenu"))
      filename = "/main-menu.jsp";
    else if (identifier.equals("news"))
      filename = "/news/news.jsp";
    else if (identifier.equals("help"))
      filename = "/help/help.jsp";
    else
      throw new RuntimeException("Invalid Request Target");
  }

  public String getIdentifier() {
    return identifier;
  }

  public String getFilename() {
    return filename;
  }
}
 

这非常直观,主要方式是考虑您的路径并将它们全部存储在一个地方 —— LookupBean。

这种方法有一个明显的副作用值得注意,那就是它能帮助您组织 VXML 文件和路径。看前面的 清单 3 中 LookupBean 的代码,注意此应用程序所用的 JSP 文件是怎样垂直排列在一起的。以这种方式查看时,很容易看出,路径和目录结构极为一致。这可提醒您已有哪些文件和 JSP,通常还会提醒您保持一致。换句话说,您不希望将一个帮助文件放在 /help/help.jsp 中,而另一个放在 /about/help.jsp 中。 如果您的代码中未使这两个 JSP 文件中的任何一个在 100 行内列出另外一个文件,那么很容易这样做,但使用 LookupBean 就能立即发现这样的错误。

JSP 常量

将文件名换成常量即可使 LookupBean 略加改进,常量在 LookupBean 或另外一个 java 类中定义,如 清单 4 所示。

清单 4. 在 PathConstants 类中定义常量

package com.ibm.vxml;

public class PathConstants {

  public static final String ACCOUNT_INFORMATION_URL = "/profile/account-information.jsp";
  public static final String MAIN_MENU_URL           = "/main-menu.jsp";
  public static final String NEWS_URL                = "/news/news.jsp";
  public static final String HELP_URL                = "/help/help.jsp";

  // etc...
}

现在更改 LookupBean 以使用这些常量,如 清单 5 所示。

清单 5. 使用 PathConstants 类的 LookupBean

package com.ibm.dw.voicexml;

public class LookupBean implements java.io.Serializable {

  private String identifier;
  private String filename;

  public LookupBean() { }

  public void setIdentifier(String identifier) {
    this.identifier = identifier;
    if (identifier.equals("accountInformation"))
      filename = PathConstants.ACCOUNT_INFORMATION_URL;
    else if (identifier.equals("mainMenu"))
      filename = PathConstants.MAIN_MENU_URL;
    else if (identifier.equals("news"))
      filename = PathConstants.NEWS_URL;
    else if (identifier.equals("help"))
      filename = PathConstants.HELP_URL;
    else
      throw new RuntimeException("Invalid Request Target");
  }

  public String getIdentifier() {
    return identifier;
  }

  public String getFilename() {
    return filename;
  }
}

这一更改似乎微不足道,甚至有些麻烦。但在开发应用程序时,有时您可能要在 LookupBean 以外的类中使用那些路径常量。通过将文件名换成单独类中的常量,您就可以在应用程序中的任何位置 轻松地重用那些常量。

进一步利用 java
既然已经掌握了基础,下面再进一步,看看 java 类是怎样切实协助您开发应用程序的。不要忘记,您希望使您的 VXML 轻便短小 —— 无线设备不想处理大文件。很容易就能编写出 JSP 页面或 servlet 来输出轻量级 VXML 文件,同时依然要使用大量复杂难懂的代码。输出可能很小,但生成输出的代码的维护工作极为艰难。

避免这一陷阱的最简单的方法就是尽可能在所有地方都使用 java 类。将实用工具和可重用代码改为普通旧式 java 对象(POJO),这样实际生成 VXML 的 JSP 页面和 servlet 就能保持简单、可维护。这部分内容介绍几种能完成这一任务的有用工具和技术,它们都使用简单的 java 类。

封装静态 VXML

您已经看到,可以将那些更改不甚频繁的 VXML 放在一个静态文件中,然后按需在应用程序中读入此文件。这样就解决了在多个文件中(JSP 页面或 servlet)重复键入相同的 VXML 的问题,还解决了多个文件中具有相同的 VXML 所造成的维护难题,因为您能够在不需更新其他 VXML 的情况下更新一组 VXML。但这种方法也有一个显著的问题。

 课后小练习

与 清单 2 中所示方法类似,清单 6 有许多可以改进的地方。可以非常轻松地重载 writeFile() 方法,添加一个额外选项(可使用 File 甚至是 InputStream 或 Writer),使文件名更具灵活性,也可添加更多用于输出的选项(例如除 OutputStream 之外再加一个 Writer)。添加几个这样的选项可使此类更加有用。
 
 
使用这种技术,每次访问使用某个片段的 JSP 页面或 servlet 时,都要付出打开、缓存和读入那个文件的成本。如果您将法律声明或部分标准头部/脚注(header/footer)放在一个静态文件中,这个问题就会相当严重。每次访问使用那些 VXML 片段的页面时,就要读取和输出该文件。这并非 好事 —— 特别是考虑到无线用户在获取内容时往往最缺乏耐性。没有人愿意为您的应用程序而等待,他们希望的是在电话上点击链接,立刻阅读所请求的内容,然后去做其他事。

避免这个问题的最简单的方法就是通过 java 类读入静态内容。只需进行简单的更改,如 清单 6 所示。

清单 6. 使用 java 类读入文件
   
package com.ibm.dw.voicexml;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

public class FileLoader {

  private static Map fileList = new HashMap();

  private static void loadFile(String filename) throws IOException {
    if (fileList.containsKey(filename)) {
      // We already have this file loaded
      return;
    } else {
      BufferedInputStream bis = null;
      try {
        StringWriter writer = new StringWriter();
        bis = new BufferedInputStream(
          new FileInputStream(
            new File(filename)));
        int readBytes = 0;
        while ((readBytes = bis.read()) != -1) {
          writer.write(readBytes);
        }
        fileList.put(filename, writer.toString());
      } finally {
        if (bis != null) bis.close();
      }
    }
  }

  public static void writeFile(OutputStream out, String filename)
    throws IOException {

    // Load the file we want to output
    loadFile(filename);

    // Output the file
    String fileContent = (String)fileList.get(filename);
    byte[] data = fileContent.getBytes();
    out.write(data, 0, data.length);
  }
}
 

这个类与上文 清单 1 所示的 servlet 代码和 清单 2 中的实用方法极为相似,但也有一些显著的差异。首先,尽管类以相同的方式读入文件(在 loadFile() 方法中),但仅读入一次。读入文件后,就会将文件名及其内容传递到 Map 中,此后再也不会重新读取这个文件。这是一段非常简单的代码,但确实很好地实现了优化。

当然,这些都是静态方法,也就是说,您不需要实例化 FileLoader 的实例就可以使用它(可以使用单元素方法,但就此处介绍的内容而言,那似乎过于小题大做了)。您希望确保开发人员不会 实例化这个类,希望保持文件名和内容的单一映射,以实现最优化。

预载入文件

使用 FileLoader 类的惟一弊端就在于,在请求特定文件之前不会遇到错误。那通常是在用户实际访问应用程序时发生的,也是定位和报告错误的最差 时机。举例来说,假设您在一个名为 about.vxml 的外部文件中存储公司相关信息。由于这些信息非常静态,所以可使用 FileLoader 类,在请求某个文件时载入它,同时避免了将这些信息放在 JSP 页面内(这是一个不错的选择,您的公司简介更改得有多频繁?)

现在假设,在一次例行备份中 about.vxml 文件被损坏,没有人知道究竟出了什么问题,但现在 about.vxml 正处于您的文件服务器上,随时向任何访问它的用户提供垃圾信息。两天后,一位潜在客户用 PDA 访问了您的站点,试图获得您的公司联系信息和 about 页面。突然,应用程序崩溃了,您的用户非常沮丧,您失去了这项业务。这可不怎么样!

或许避免这种情况的最简单的方法就是预载入那些常用文件(例如 about.vxmml)。这样做有两个优点:

节约了用户首次请求这些页面时的载入时间。访问某一页面的第一个用户不再需要等待,您可在应用程序启动时载入页面,没有任何 用户需要等待。
避免使用户遇到上面的例子中叙述的那种错误,如果存在错误,将在应用程序启动时被捕获和报告。
为应用程序使用这种特性非常简单。首先,需要在 FileLoader 类中将 loadFile() 方法更改为 public,这样即可载入而不必输出文件,清单 7 展示了所需更改。

清单 7. 对 FileLoader 略加更改
   
package com.ibm.dw.voicexml;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

public class FileLoader {

  private static Map fileList = new HashMap();

  public static void loadFile(String filename) throws IOException {
    if (fileList.containsKey(filename)) {
      // We already have this file loaded
      return;
    } else {
      BufferedInputStream bis = null;
      try {
        StringWriter writer = new StringWriter();
        bis = new BufferedInputStream(
          new FileInputStream(
            new File(filename)));
        int readBytes = 0;
        while ((readBytes = bis.read()) != -1) {
          writer.write(readBytes);
        }
        fileList.put(filename, writer.toString());
      } finally {
        if (bis != null) bis.close();
      }
    }
  }

  public static void writeFile(OutputStream out, String filename)
    throws IOException {

    // Load the file we want to output
    loadFile(filename);

    // Output the file
    String fileContent = (String)fileList.get(filename);
    byte[] data = fileContent.getBytes();
    out.write(data, 0, data.length);
  }
}
 

现在,您可以创建 servlet 或 JSP 页面,多次调用 loadFile() 方法,每次配合使用希望载入的一个文件,如 清单 8 所示。

清单 8. 调用 loadFile() 方法
   
  FileLoader.loadFile("about.vxml");
  FileLoader.loadFile("help.vxml");
  FileLoader.loadFile("legal.vxml");
  FileLoader.loadFile("header.vxml");
  FileLoader.loadFile("footer.vxml");
  // etc...

有几个位置可以放置这段代码,但我通常会将它放在 servlet 的 init() 方法中,类似于 清单 9。

清单 9. 在 servlet 启动时载入 VXML 文件
   
// Lots of import statements here, omitted for brevity

public class InitServlet extends HttpServlet {
  public void init(ServlertConfig config) throws ServletException {
    super.init(config);
    try {
      List filesToLoad = MyApplicationConstans.getSomeListSomehow();
      for (Iterator i = filesToLoad.iterator(); i.hasNext(); ) {
        String filename = (String)i.next();
        FileLoader.loadFile(filename);
      }
    } catch (Exception e) {
      throw new ServletException(e);
  }
}

这段代码中没有什么值得特别说明的。init() 方法获取要载入的文件列表,然后遍历列表,从列表中载入各文件(实现这一文件列表的代码由您自行完成!),并且报告错误。如果有问题,此时您就会发现,而不用等到用户访问应用程序时。

init() 方法在 InitServlet 载入后立即运行,但 InitServlet 只有在某人访问 servlet 时才会载入,不会自动载入(至少需要您做一些处理)。为确保 servlet 确实 在您的应用程序启动后立即载入,您应在 web.xml 文件中为这个 servlet 创建一个新条目:

<servlet>
 <servlet-name>InitServlet</servlet-name>
 <servlet-class>InitServlet</servlet-class>
 <load-on-startup>1</load-on-startup>
</servlet>

这确保 servlet 在应用程序启动后立即载入(这次是自动的)。应用程序联机之后、用户访问应用程序之前,您的静态 VXML 文件会全部载入,此时您可以处理任何问题。

标准化头部和脚注
另外一个常见问题源于另一种常用技术,也就是在 VXML 输出结果中输出头部和脚注。这也是 HTML 和 XML 领域中的常见问题,您通常也会看到与本节介绍的方法类似的问题解决之道。

问题在哪里?

为更好地理解这个问题,请考虑一个简单的 VXML 文件,如 清单 10 所示(该文件最初在本系列的第一篇文章中详细介绍过)。

清单 10. 一个非常简单的 VXML 文件

<?xml version="1.0" encoding="UTF-8"?>

<vxml version="2.1">
  <form id="MainMenu">
    <field name="instrument">
      <prompt>What is your  favorite musical instrument?</prompt>

      <!-- Insert an inline grammar -->
      <grammar type="text/gsl">
        [guitar mandolin dobro (violin fiddle) banjo]
      </grammar>

      <!-- Handle the case when they give no answer -->
      <noinput>
        Did you say something? I didn't hear you.
        <reprompt />
      </noinput>

      <!-- Handle the case when no match is found -->
      <nomatch>
        I suppose that's OK, but it's not on my top five.
        Want to try again?
        <reprompt />
      </nomatch>
    </field>

    <!-- Handle the various options. -->
    <filled namelist="instrument">
      <if cond="instrument == 'guitar'">
        <prompt>That's right! Hang up and go practice.</prompt>
      <elseif cond="instrument == 'mandolin'" />
        <prompt>Nice... and only four strings to keep in tune.</prompt>
      <elseif cond="instrument == 'dobro'" />
        <prompt>Boy, that's no fun to learn, is it?</prompt>
      <elseif cond="instrument == 'violin'" />
        <prompt>We call that a fiddle, Mr. Fancy Pants.</prompt>
      <elseif cond="instrument == 'fiddle'" />
        <prompt>Does playing classical music on a
        fiddle make it a violin?</prompt>
      <elseif cond="instrument == 'banjo'" />
        <prompt>Wow, I hope you live alone.</prompt>
      </if>
    </filled>
  </form>
</vxml>
 

尽管看起来并无相似之处,但每个 VXML 文件的特定于页面的内容之前都有如下代码:

<?xml version="1.0" encoding="UTF-8"?>

<vxml version="2.1">

类似地,每个 VXML 文件结尾处都包含如下代码:

</vxml>

看上去文件与文件之间似乎并没有太多的共同之处,是这样吗?但考虑一下,在 VXML 文件间的共同点方面,这真的是 “最小公分母”。例如,您可能决定在每个 VXML 页面中为一名用户提供返回主菜单的选项。这可能需要下面这样的语法:

  <link next="#Menu">
    <grammar type="text/gsl">[menu begin start (start over)]</grammar>
  </link>

不要将它添加到每一个 VXML 页面中,而是添加到各 VXML 文件的通用信息集中:

<?xml version="1.0" encoding="UTF-8"?>

<vxml version="2.1">
  <link next="#Menu">
    <grammar type="text/gsl">[menu begin start (start over)]</grammar>
  </link>

即使只添加如此少的代码,使用一种方法来标准化 VXML 文件的开头和结尾处所包含的内容仍然会有很大的帮助。您消除了因为在一个文件中更改头部或脚注而在另一个文件中没有更改时造成的错误,另外也确保了不会出现没有 “主菜单” 链接(或您希望页面共有的其他任何通用功能)的单独页面。

先加和后加内容

您可以直接使用另外一个 java 实用工具类来处理标准化内容的先加(prepending)和后加(post-pending)。您已经看到,使用 java 类向 VXML 输出中插入内容非常简单,这是另外一种同样方法适于多种目的的情况。

假设您只想使用标准 VXML 头部:

<?xml version="1.0" encoding="UTF-8"?>

<vxml version="2.1">

您可以使用类似于 清单 11 的方法来处理输出这个头部。

清单 11. 输出标准头部
   
public static void printHeader(OutputStream out) throws IOException {
  out.write(VXMLConstants.HEADER_BYTES, 0, VXMLConstants.HEADER_BYTES.length);
}

随后,VXMLConstants 源文件中可能具有下面这样的内容:

public static final String HEADER_STRING =
  "<?xml version="1.0" encoding="UTF-8"?>" +
  "<vxml version="2.1">";
public static final byte[] HEADER_BYTES = HEADER_STRING.getBytes();

有很多种方法可以达到相同的效果,这只是其中一种,但要点在于,您现在可以从 servlet 或 JSP 页面中调用 printHeader() 来输出标准头部。可以传入一个 JSP 页面或 servlet 提供的 OutputStream,使 java 类处理标准头部文本的输出。随后,若您想更改或添加 VXML 为该头部,只要更改 VXMLConstants 类中的 HEADER_STRING 常量即可。通过这一次更改(以及重新编译),所有 VXML 文件的头部都会更新。

为脚注实现类似的策略轻而易举(这个问题留作课后练习)。与头部类似,可以轻松入手 —— 可能只需要使用一个 </vxml> 元素来结束 VXML —— 并按照自己的需求添加通用 VXML。

最终结果很可能是,每一个 JSP 页面或 servlet 都以 printHeader() 开头,以 printFooter() 结尾,至少在输出 VXML 的那一节中是这样。

打包内容

虽然添加这些方法确实是非常有用的技术,但也必须对程序员进行一些额外的培训。务必确保 头部和脚注匹配,否则会造成严重的问题。切记,在绝大多数情况下,要在头部处开始元素,在脚注处结束这些元素。最简单的情况包括开始和结束 vxml 元素。但向头部和脚注添加更多通用内容时,您可能希望在头部中开始多个嵌套的元素(例如,在头部开始一个 VXML 表单),并在脚注中结束这些元素。

如果忘记了包含 printHeader() 或 printFooter() 方法,您的 VXML 就不会生效,有一些在头部开始的元素未在脚注中结束,或反之。最终结果在最好的情况下是外观奇怪的 VXML,在最糟糕的情况下则是应用程序彻底崩溃。无论哪种都不会给您带来忠实用户和老客户,也就是说,这两种情况对于无线和 VoiceXML 开发都是不可接受的。

最简单的方法(尽管也是最 “人工” 的方法)就是开发某种类型的编码标准。指导您的开发人员 “做正确的事”,给予开发人员足够的培训,使他们能够插入正确的头部和脚注语句。这种方法无疑易受人类失误的影响,但有着简单之美。

如果您发现这种方法无效,可能需要推行更加严厉的方法。假设有一些开发人员没有 “做正确的事”,而主管人员没有足够的魄力去采取必要的步骤,使这些开发人员做自己应该做的事情,此时,您要找到一种方法,确保每个文件中都使用头部和脚注,并接管开发人员的部分权力,使他们无法再把事情弄糟。

在这种情况下,您可以设置好一切,以使开发人员将他们的内容传递到一个处理实际 VXML 输出的方法。清单 12 展示了一个接受字符串输入的方法,它处理字符串的输出以及先加和后加头部与脚注。

清单 12. 控制 VXML 输出
   
    public static void print(OutputStream out, String vxmlContent)
    throws IOException {

    byte[] data = vxmlContent.getBytes();
    printHeader(out);
    out.write(data, 0, data.length);
    printFooter(out);
  }

看上去比预想的简单得多,方法接受一个表示输出目标的流和采用字符串形式的 VXML,然后输出它。但它确实保证了头部和脚注总是会得到处理。

似乎有点奇怪,因为现在您的 JSP 页面和 servlet 不直接输出 VXMLT,而是将内容传递到一个实用方法,由该方法来处理全部输出。无论如何,您能够更充分地控制 VXML 输出;因为 JSP 页面和 servlet 不再直接输出,您就可以确保头部和脚注均已打印、输出流已关闭(只有在使用 servlet 或有大量缺乏经验的开发人员时,这才会真正成为一个问题)、资源得到了管理。如果是从数据库中提取数据库,或者开发人员确实不注意处理头部和脚注,那么这不失为一种解决问题的好办法。

但您应该更多地将此视为一种中间措施,因为有时开发人员可能会刻意不去使用标准头部或脚注,而这种方法增加了此类开发技巧的难度。不必停止使用头部或脚注,手工输出它,您可以处理整个 VXML 输出。但再次说明,作为一种纠正性的措施,仍然是值得您纳入 VoiceXML 工具箱的一种不错的技巧。

结束语

本文为您介绍了许多可纳入开发人员工具箱的有用工具和技巧。开发应用程序时,其中的每一种都能满足某种重要的目的,您应该动手去尝试这些工具,在下一个 VoiceXML 项目中至少利用其中一种。

示例代码下载

您可以参阅本文在 developerWorks 全球站点上的 英文原文

java 理论和实践: 理解 JTS - 幕后魔术 : 阅读本系列文章的第二部分。
 
java 理论和实践: 理解 JTS - 平衡安全性和性能 : 阅读本系列文章的第三部分。
 
Jim Grey 与 Andreas Reuter 合著的 Transaction Processing: Concepts and Techniques 是关于事务处理这个主题的权威性著作。

Philip Bernstein 与 Eric Newcomer 合著的 Principles of Transaction Processing 是关于这个主题的一篇优秀介绍性文章;它涵盖了这个主题的许多历史以及概念。
 
java Transaction Service 规范的可读性很好,它深入说明了对象事务监视器如何适应分布式应用程序。
 
java Transaction API(JTA) 规范详细说明了 J2EE 中事务性支持的低层细节问题。

J2EE 规范 描述了 JTS 和 JTA 如何适应 J2EE 以及事务如何与其他 J2EE 技术(如 Enterprise JavaBeans 技术)进行交互。

“Transaction Logging Concepts” 对如何实现事务日志以及如何进行回滚和重新启动恢复做了精彩的解释。

Supporting open standards for Web services and J2EE(PDF)是 IBM 的一本白皮书,它提供了关于事务如何适应 Web 服务世界的真知灼见。
 
Anbazhagan Mani 和 Arun Nagarajan 合著的 “Understanding quality of service for Web services”(developerWorks,2002 年 1 月)讨论了事务应该如何通过 ACID 测试。

请阅读完整的 java 理论与实践 系列。
 
请在 developerWorks java technology 专区 上查找其他与 java 有关的文章和教程。 

抱歉!评论已关闭.