您的位置:首页 > 编程语言 > Java开发

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

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

现 在您通常是在开发环境中开发应用程序,然后再将它们部署到生产环境中。大多数时候,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.Writer
java.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 上的结果



提醒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 中的代码,
urlConn
JarURLConnection
的一个实例。在调用
JarURLConnection
getInputStream
时,它调用
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 应用程序的性能测试



陷阱上述场景非常常见,并且很容易在功能测试中验证。然而,在性能测试中,当企业应用程序经历很高的事务处理速度(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 上。

对于容易出错的网络程序,应该成对地执行读取和写入操作。如果将数据写入某个套接字,则必须有某个进程去读取它们。


共享本文……




请 Digg 该故事



发布到 del.icio.us



Slashdot 一下!

参考资料

学习

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

Enable C++ applications for Web services using XML-RPC”(developerWorks,2006 年 6 月):阅读这个循序渐进的指南以将 C++ 方法作为服务来公开。

Rational Application Developer 信息中心:访问这个信息中心以了解如何使用该集成开发环境。

WebSphere Application Server Test Environment Guide:获得有关 WebSphere Test Environment 的最新详细信息。

按主题搜索“AIX and UNIX”库:

系统管理

应用程序开发

性能

移植

安全性

提示

工具和实用程序

Java technology

Linux

开放源代码

AIX and UNIX:“AIX and UNIX developerWorks”专区提供了大量与 AIX 系统管理的所有方面相关并扩展您的 UNIX 技能的信息。

AIX and UNIX 新手入门:访问“AIX and UNIX 新手入门”页面以了解更多关于 AIX 和 UNIX 的内容。

AIX 5L™ Wiki:AIX 相关技术信息的协作环境。

Safari 书店:访问这个电子参考库以查找特定的技术资源。

developerWorks 技术事件和网络广播:了解最新的 developerWorks 技术事件和网络广播。

Podcasts:收听 Podcast 并与 IBM 技术专家保持同步。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: