将 Java Web 应用从 Windows 移植到 AIX 时需要注意的问题

消除三个常见的“陷阱”

研究您在将 Java™ Web 应用程序从 Windows® 移植到 AIX® 时可能会陷入的三个陷阱,并了解如何避免它们。在开发应用程序时,您通常是在开发环境中进行,然后再将它们部署到生产环境。如果您在 Windows 上进行开发并部署到类 UNIX® 平台,则可能会遇到一些微妙的陷阱。第一个陷阱是在 Windows 和 AIX 中设置不同的行分隔符,第二个陷阱说明了在不同开发环境和操作系统中定位文件的最佳方法,第三个陷阱是套接字通信中的一个常见异常。

Shu Fang Rui (shufangrui@gmail.com), 软件工程师

Shu Fang Rui 是上海交大(中国)的研究生。她对无线技术和 Web 服务感兴趣。除了旅行之外,她还喜欢其他各种运动。



2007 年 3 月 26 日

引言

现在您通常是在开发环境中开发应用程序,然后再将它们部署到生产环境中。大多数时候,Windows® 是开发平台的理想选择,因为存在如此多强大的集成开发环境(Integrated Development Environment,IDE)可供使用。诸如 UNIX、Linux® 或 AIX® 等类 UNIX® 平台则由于其稳定性而成为理想的生产平台。Java™ 被宣称是高度平台无关的编程语言,具有所谓的编写一次,到处运行 特性。在大多数情况下,当在不同平台之间移植时,此特性可以为开发人员节省许多时间。然而,您应该知道一些陷阱 或缺陷,以确保您的应用程序在目标平台中完全按您希望的那样操作。

本文将讨论可能会在移植过程期间陷入的三个陷阱。其中提供了帮助您避免陷阱的信息,以便您安全地享用 Java 编程语言的强大功能。


HTTP 通信问题

HTTP 通信在每种 Web 应用程序中都非常普遍。每当调用某个 Servlet 或 JavaServer Pages (JSP),就会发生 HTTP 通信。虽然 HTTP 协议是平台无关的,但是在不同平台之间通信时需要注意一些特殊的事项。

在本场景中,一个客户端对网关发起一个特殊请求,网关处理该请求,然后向客户端发回一个响应。该客户端使用基于 XML 的专有协议来与网关通信,并且网关仅处理遵守该协议的消息。该协议在 <Name> 和 <Greeting> 这两个 XML 元素之间需要一个换行符。

清单 1 中的代码所示,该请求的正文添加了一个换行符。但是,服务器是否顺利地处理它并正确地响应呢?视情况而定。这是在跨不同平台移植 Java 应用程序时的一个常见问题。

清单 1. 客户端发出一个 HTTP 请求
try {
  URL url = new URL("http://localhost:9081/SampleWeb/Simulator");
  URLConnection conn = url.openConnection();
  conn.setDoOutput(true);
  conn.setRequestProperty("Content-Type", "application/xml");

  OutputStream os = conn.getOutputStream();
  PrintWriter writer = new PrintWriter(os);

  writer.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>");
  writer.println("<Name>");
  writer.print("<first name>");
  writer.print(“Rachel");
  writer.println("</first name>");
  writer.println("</Name>");

  //A line break is required here
  writer.println();

  writer.println("<Greeting>");
  writer.println("Hello!");
  writer.println("</Greeting>");

  writer.flush();
  conn.getInputStream();
} catch (MalformedURLException mue) {
  System.err.println("error, message =" + mue);
} catch (IOException ioe) {
  System.err.println("error, message =" + ioe);
}
陷阱
该代码在 Windows 上的开发环境中工作得很好,但是在部署到 AIX 上的生产环境中以后,您会惊奇地发现网关没有返回任何响应。那么这段似乎正确的代码出了什么问题呢?

不在您的控制之内的网关是一个运行于 Windows 平台上的 C 程序。它错误地假设所有接收到的请求都来自 Windows,并且 \r\n 换行符应该位于 <Name> 和 <Greeting> 元素之间。因此,它尝试将该请求分析为在 <Name> 和 <Greeting> 之间具有一个 \r\n 字符。然而,在 AIX 和大多数类 UNIX 平台上,如果没有预先设置 line.separator Java 系统属性变量,则其缺省值为 \n,这就是网关抱怨请求格式不正确的原因。

解决办法
一旦您知道了出错的原因,解决此问题是相当简单的。可以在客户端或网关代码中进行修复。
  • 如果您无法控制网关代码,则只能使用 “System.setProperty(“line.separator", "\r\n")" ; 对客户端进行硬编码。
  • 否则,应该让网关代码以适当的方式处理不同的平台。对于类 UNIX 平台,可以将 \n 字符处理为换行符。对于 Mac OS,应该将 \r 处理为换行符。在 Windows 平台上,应该将 \r\n 处理为换行符。
提醒
要注意 Java 应用程序编程接口 (API),例如 java.io.Writerjava.io.Reader 和继承它们的 API。它们全都是基于字符的 API,并且在没有设置行分隔符时,它们将从系统属性获得缺省行分隔符值。如果不需要严格的字符格式,您应该考虑使用基于字节的 Java API 以实现更好的性能。

当在不同平台之间移植时,硬编码平台相关的内容通常是 Java 应用程序失去兼容性的原因之一。行分隔符只是最常见的常量之一。可能的内容还包括文件分隔符、路径分隔符,等等。当您希望在代码中包括这些常量时,应该使用 System.getProperty("property name") 来获得属性值而不是硬编码该字符。

定位文件

在不同平台之间移植 Java 应用程序时的另一个常见问题是对文件进行定位。不同的环境中有不同的文件定位方法。

在本场景中,假设您希望定位某个实用程序 Java 项目中的一个 DTD 文件,该文件被某个企业应用程序项目中的一个 Web 项目使用。若要定位 WebSphere® Studio Application Developer (Application Developer) V5.1.2 中的 DtdEntityResolver 类中的 sample.dtd 文件,您可以编写清单 2 中的代码,它将获得类似于 E:/workspace/UtilProj/bin/com/ibm/util/sample.dtd 的路径。

清单 2. 用于定位文件的示例代码
Class clazz = getClass();
URL url = clazz.getResource("."); //Trying to get the URL of current directory
String currentPath = url.getPath(); 
String filePath = currentPath + "sample.dtd";

查看此代码之后,您可能会说,这非常好,我拥有了一个更好的解决方案。的确存在一个更好的解决方案,但是让我们首先使用此代码,它在 Application Developer V5.1.2 的 WebSphere Test Environment 中工作得非常好。通过这种方式,您对该文件进行了定位。

在完成所有其他模块以后,您的团队决定将该企业应用程序项目部署到生产环境——运行于 AIX 之上的 WebSphere Application Server (Application Server) V5.1。这次,您没那么幸运了。代码引发了 java.lang.NullPointerException 并且您定位文件失败。

陷阱
为什么会发生这种情况呢?它在 Windows 上工作得非常好,但是在 AIX 上却失败了。这是否是 Java 代码的跨平台错误?起初您可能会这样认为。然而,情况并非如此。让我们再次查看上述代码所产生的文件路径。它是 $Workspace/$ProjectName/$bin/$packageName/sample.dtd。您的项目主目录中有一个 bin 目录,该目录用于存储编译后的二进制类。当您在运行于 AIX 上的 Application Server 中部署企业存档(Enterprise Archive,EAR)文件之后,是否还存在一个 bin 目录呢?正如您所知道的,在将企业项目导出为 EAR 之后,实用程序 Java 项目将包括在一个 Java 存档(Java Archive,JAR)文件中。在 JAR 文件中,您无法使用“.”(当前目录指示符)来定位资源,因此 java.lang.Class.getResource(".") 返回一个 Null 对象。

弄清这一点之后,对于运行在 Windows 平台上的独立 Application Server,您可能认为上述代码可能也会引发同样的 NullPointerException。当将相同的 EAR 部署在独立 Application Server 而不是内置 WebSphere Test Environment 中时,将会发生同样的错误。您的代码在 Application Developer V5.1.2 附带的测试环境中和在 Application Server 5.1.x 中具有不同的行为(即使两者都运行在同样的 Windows 平台上),这听起来非常奇怪。对于企业应用程序项目中的 Java 项目,WTE 直接从您工作区中的 bin 目录加载二进制类,而独立应用程序服务器则从已部署的 JAR 文件加载它们。如果您对两个环境之间的比较很感兴趣,请参见 Rational® Application Developer 信息中心(请参见参考资料)。有关 WebSphere Test Environment 的更多详细信息可以在 WebSphere Application Server Test Environment Guide 中找到(请参见参考资料)。

在 Rational Application Developer V6.0 中,该测试环境旨在作为一个独立应用程序服务器,因此作为测试环境的 Application Server 和作为独立服务器的 Application Server 之间的差异不复存在。上述代码在 Rational Application Developer V6.0 和在独立 Application Server 6 上具有相同的行为,无论是在 Windows 还是在 AIX 上。NullPointerException 始终会被引发,因为两个环境都将企业应用程序项目中的实用程序 Java 项目视为一个 JAR 文件。

解决办法
既然您知道了为什么会出错,下面就让我们了解一下更好的解决方案:使用 getClass().getResource("sample.dtd")。这里,java.lang.Class.getResource(String filename) 将资源查找任务委托给关联的 ClassLoader。无论文件是在 JAR 中还是在 bin 目录中,它都始终返回解析后的文件路径。图 1 显示了 Windows 和 AIX 平台上不同运行时环境之间的比较。

请注意,在下面的图 1 中,java.lang.Class.getResource(String filename) 在每种环境中都可以正常工作,无论是内置的 Application Developer 测试环境、运行在 Windows 上的独立应用程序服务器还是运行在 AIX 上的独立应用程序服务器。结论是,java.lang.Class.getResource(String filename) 始终是确保平台可移植性的首选方法。

图 1. getResource(fileName) 在 Windows 和 AIX 上的结果
getResource(fileName) 的结果
提醒
JAR 文件被打包为 ZIP 文件格式,因此您可以将它们用于类 ZIP 任务,例如无损数据压缩、归档、解压缩和存档解包。在您使用 getClass().getResource(String filename) 来定位文件 URL(比如 $INSTALLEDAPP_HOME/SampleEAR.ear/UtilProj.jar!/com/ibm/util/sample.dtd)以后,下一个任务是读取 JAR 文件中的内容;请参见清单 3
清单 3. 读取 JAR 文件内容的错误方法
URL jarUrl = getClass().getResource(“simple.dtd");
String path = fileUrl.getPath();
FileInputStream fis = new FileInputStream(path);

读取 JAR 文件中的内容是相当棘手的。清单 3 显示了一种获得文件 simple.dtd 的 FileInputStream 的直观方法,但是它无效。此方法会引发 Java.io.FileNotFoundException。请参见清单 4清单 5 以获得正确的方法。

清单 4. 读取 JAR 文件内容的正确方法
URL jarUrl = getClass().getResource(“simple.dtd");
URLConnection urlConn = jarUrl.openConnection();
InputStream is = urlConn.getInputStream();
清单 5. 读取 JAR 文件内容的正确方法
InputStream is = getClass().getResourceAsInputStream("simple.dtd");

为什么 new FileInputStream(String name) 无效而 URL.openConnection().getInputStream() 却有效呢?因为 java.net.URL 的每个实例都与某种协议关联,例如 HTTP、JAR、文件,等等。而且,每种协议都具有特定的处理程序(此处理程序是 java.net.URLStreamHandler 的实例),以处理相关协议的连接详细信息。URL.openConnection() 调用 URLStreamHandler.openConnection() 来获得 URLConnection 对象,此对象表示到该 URL 所引用的远程对象的连接。对于 HTTP 协议,将会返回一个 HttpURLConnection 对象;对于 JAR 协议,将会返回一个 JarURLConnection 对象。

对于清单 4 中的代码,urlConnJarURLConnection 的一个实例。在调用 JarURLConnectiongetInputStream 时,它调用 JarFile.getInputStream(JarEntry jarEntry)jarEntry,在您的例子中是名为 simple.dtd 的文件。最后返回一个 JarInputStream 实例,用于读取 JAR 文件中的内容。

FileInputStream 无法正确工作的原因可能非常明显——JAR 文件中的 JarEntry 使用 Jar 协议。FileInputStream 仅处理文件协议,因此它自然无法成功处理 sample.dtd(JAR 文件中使用 JAR 协议的 JarEntry)。

对于清单 5 中的代码,getClass() 返回的类 literal 调用 ClassLoader.getResourceAsInputStream()。后者又调用 getResource(fileName).openConnection().getInputStream()清单 5 中的代码在功能上等效于清单 4 中的代码。

总而言之,在读取 JAR 文件中的内容时,应该使用清单 4清单 5 中的代码;决不要使用 FileInputStream,因为它无法处理 JAR 协议。

套接字通信中没有读取过程

本部分讨论在 AIX 平台上的套接字通信中经常遇到的一个问题。性能测试是一件好事情。它可以帮助您找出不如功能测试中找到的错误那么明显的错误。这些错误包括内存泄漏和多线程编程的争用条件。它们就像您代码中的捣蛋鬼,有时可能会使您的代码行为异常。

在这个性能测试场景中,测试客户端向运行于 Application Server 上的 Web 应用程序发送 Web 服务请求。该 Web 应用程序处理请求并构造一个新消息,然后将新构建的消息发送到一个网关。然后网关向该 Web 应用程序发回一个响应,该 Web 应用程序再向测试客户端发送一个响应。图 2 显示了该过程流。

图 2. Web 应用程序的性能测试
Web 应用程序的性能测试
陷阱
上述场景非常常见,并且很容易在功能测试中验证。然而,在性能测试中,当企业应用程序经历很高的事务处理速度(Transaction Per Second,TPS)时,如果没有预先执行性能测试,您的应用程序很可能会引发异常。

异常发生在套接字通信中,其主要特征为:

java.net.SocketException: There is no process to read data written to a pipe

“There is no process to read data written to a pipe”错误是一个特定于 AIX 的错误消息,位于对应的 Java 代码的本机方法实现中。它是由在 AIX 上实现套接字通信的 C 代码所引发的。

正如该消息所述,它是在写入某个管道的消息没有被任何进程读取时发生的。当向接收端发送大量请求时,接收端可能由于超时、线程被阻塞或其他原因而无法读取请求,然后就会引发此异常。

解决办法
大多数时候,此问题都是由潜在的错误引起的。例如:
  • 该 Web 应用程序与网关建立一个 HTTP 连接,并尝试向它发送一个请求(图 2 中的步骤 2),如清单 6 所示。
    清单 6. 向 Web 应用程序发送 HTTP 请求
    URL url = new URL(serverAddress);
    URLConnection conn = url.openConnection();
    conn.setDoOutput(true);
    OutputStream os = conn.getOutputStream();
    PrintWriter writer = new PrintWriter(os);
    writer.println("Hello, dude");
    writer.flush();
    writer.close();
    InputStream is = conn.getInputStream();

    如果没有最后一行 InputStream is = conn.getInputStream,则会引发 java.net.SocketException: There is no process to read data written to a pipe 异常。如果没有 conn.getInputStream(),则根本不会将请求消息发送到接收端。因此,接收端不会收到任何消息,当然就没有任何进程读取写入连接套接字的请求数据了,所以就导致产生异常。

  • 侦听响应时超时,如图 2 中的步骤4 所示。

    性能测试可能预期测试客户端能够在五秒内获得响应。如果响应在五秒后返回,则发出请求的测试客户端将不再处理它。因此,测试客户端不会读取响应数据。对于套接字管道,数据是写入了,但是它缺少读取过程。

  • 响应线程被另一个线程阻塞,如图 2 中的步骤 3 所示。

    假设您在网关中管理线程池以响应 Web 应用程序发送的请求。当达到相当高的 TPS 时,由于低效的线程调度,很可能无法调度任何线程去处理新的请求。结果,该请求未由任何线程读取和处理。这还可能会导致错误。

提醒
对于基于输入或输出流的 API,应确保在不再需要时关闭所有打开的连接(打开的 InputStream(Reader)OutputStream(Writer))。

总结

将 Java Web 应用程序从一个平台移植到另一个平台所需的精力虽然不是非常巨大,但也是相当可观的。需要记住的三个要点如下:

  • 在编写操作系统相关的代码时,避免硬编码。使用 java.lang.System.getProtery(String name) 始终更为可靠。
  • 使用 java.lang.Class.getResource(String filename) 来定位在 Application Developer V5.1.2、Rational Application Developer V6.0 和相关版本的 Application Server 上都有效的资源,无论它们是在 Windows 还是在 AIX 上。
  • 对于容易出错的网络程序,应该成对地执行读取和写入操作。如果将数据写入某个套接字,则必须有某个进程去读取它们。

参考资料

学习

获得产品和技术

  • IBM 试用软件:使用 IBM 试用软件开发您的下一个项目,可直接从 developerWorks 下载这些试用软件。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=AIX and UNIX
ArticleID=204291
ArticleTitle=将 Java Web 应用从 Windows 移植到 AIX 时需要注意的问题
publish-date=03262007