用多字节技术开发J2EE应用 (上) 作者:尹涛 概 述 大多数J2EE服务器都能很好地支持多字节字符语言(如中文和日文),但不同的J2EE服务器、不同的浏览器支持它们方式是不一样的。当开发人员将中文(或日文)的本地应用从一个服务器迁移到另一个的时候,他们将面临多字节字符问题。本文将具体阐述产生这些多字节字符问题的根本原因和一些解决方案和建议。 中文是世界上最复杂和最详尽和语言之一。有时我觉得做为一个中国人很幸运,特别是当我看到一些朋友挣扎着学中文和写中文字时。但对于开发本地化J2EE Web应用我却不认为是件幸运的事。本文将介绍这一不幸的原因。 虽然Java平台和大多数服务器能很好地支持全球化开发,但我还是在开发基于中文和日文的应用时遇到了许多字节字符问题。 什么是编码和字符集的区别? 为什么多字节字符应用在操作系统迁移后界面的显示有所不同? 为什么多字节字符应用在应用服务器迁移后界面的显示也有所不同? 为什么多字节字符应用在IE浏览器上显示正常但在Mozilla浏览器上却不能? 为什么当使用UTF-16(全局转换格式)编码的应用在绝大多数J2EE服务器上的显示效果很差? 如果你正面临这些问题,本文将有助于你寻找到答案。 开发中各环节造成的显示问题 J2EE应用开发包含一些环节(见图1),它们中的每个都可能会造成多字节显示问题。 1. 代码环节 当你在编写J2EE应用的代码是,你很有可能使用一些如JBuiler或NetBeans之类的IDE开发环境或是像UltraEdit或Vi之类的编辑器。无论你使用它们中的哪种,当你在JSP、Java或HTML中写入带多字节字符文字的字符串,若你不注意就很有可能会遇到显示问题。 一个字符串是一个储存于文件中的静态信息,在不同的语言字符中使用时不同的编码方式。多数的IDE将它们的默认编码方式设置为ISO-8859-1(一个用于ASICC字符的编码),它将造成多字符的信息丢失。例如在中文版的NetBeans中默认的文件编码很不幸就是ISO-8859-1。当你用它编辑带中文字的JSP文件时,一切看似正常。但正如我前面提到的,所有这里你看到的信息都存储在内存中。所以当你关闭IDE环境,再重新打开这个文件时,你看到的是一堆乱码。原因是在用ISO-8859-1编码存储时丢失了中文字符的信息。 2. 字符编码接口 Servlet和JSP规范中有许多用处理J2EE应用中字符编码的API(应用编程接口)。对于一个servlet请求,setCharacterEncoding接口可以设置当前HTTP请求中的字符编码方式。而对一个servlet响应,setContentType和setLocale接口可以设置发送的HTTP响应的MIME头部编码。 这些接口本身不会造成问题。相反,问题的产生是由于你忘了使用它们。比如说,在一些服务器中,你能在不使用上述接口的情况下得到正确的多字节字符显示。 当处理一个servlet请求时,服务器按下面的顺序来决定请求的编码: 代码指定的设置(如用setCharacterEncoding方法进行了指定) 供应商设定的设置 默认设置 当处理一个servlet响应时的情况也是类似的。 按照上面的方法,如果你使用这些API来写代码,则所有的应用服务器都将按指定的设置来选择字符编码方法。否则,不同的服务器上的编码效果就会不同。一些厂商用HTTP表单中的隐藏字段来确定请求的编码方式,而另一些则用它们自己的配置文件来设置。默认的设置也会不同,大多数采用ISO-8859-1作为默认设置。因此,一些基于多字节字符的应用会从迁移到另一个厂商的J2EE服务器时出现显示问题。 3. 编译环节 当配置正确时,你可以在编写代码时将多字节字符串存储在源文件中,但这些源代码文件并不能正确执行。如果你在写servlet代码时,这些Java文件必须被编译成CLASS文件才能部署到应用服务器上。而对于JSP,应用服务会自动地将它们编译然后执行。在编译阶段,字符编码的问题仍然可能发生。 通过编程实例,我们得到如下的结论: Java编译器用系统环境默认的编译方式,Java运行环境也是如此; 只有第一个结果是正确的,其它的都不正确; 只有当运行环境的编码与存储源文件时的编码相同时,多字节字符才能正确地显示(否则你必须做一定的编码转换)。 4. 服务器配置环节 当你运行你的J2EE应用时,你应该将应用配置为能适应特殊的需求。在上一节中,我们看到,不同的语言设置会导致源文件中写的字符串显示不正常。其实,有不同层次的配置,它们都会造成多字节字符问题。 1) 操作系统层 操作系统的语言支持是极为重要的。服务器上的语言支持将影响JVM的默认编码设置,并且在客户端的一些设置(如字体)也会直接影响字符的显示,但这不是本文的侧重。 2) J2EE应用服务器层 多数服务器有一个服务器配置文件来设置默认的编码属性。例如,用参数“javaEncoding”来定义JSP文件生成Java文件时,Java文件编码默认为UTF-8。那意谓着如果你用GBK将中文字存在JSP文件中,并想用UTF-8编码来显示它们,错误就不可避免了。 3) JVM(Java虚拟机)层 多数服务器在同时有多个进程在运行,并且每个服务器进程有一个独立的JVM进程。你可以对不同的JVM进程进行不同的设置。大多数服务器每个进程有地域设置用于决定默认的语言支持。 Sun ONE应用服务器为每个进程设置独立的地域属性。这个设置表示了用于记录日志和屏幕输出的默认字符编码。 另一方面,不同的服务器会使用不同的JVM版本和不同的JDK,这些JVM和JDK又支持不同的编码标准。所有的这些造成了迁移的问题。例如,Sun ONE应用服务器和TOMCAT支持J2SE1.4,而另一些则仅支持J2SE1.3。J2SE1.4支持Unicode3.1,它有许多前一版本中没有的新特性。 4) 应用级 每个部署在服务器上的应用在运行前可用它独有的编码来进行配置。这一特性令多个应用各自用不同的语言在服务器进程中运行。例如,对于一部分应用服务器,你可以用如下的编码设置来指明你的应用所使用的编码。 在这些层面上的配置的目的是实现灵活性和可维护性。但不幸的是,从一个应用服务器迁移到另一个时会发生问题。因为不是所有的配置都遵从标准。例如,你从一个支持“地域字符集信息”设置的服务器迁移到一个不支持的服务器上时,你将遇到极大的困难。 5. 运行环节 在运行环节,你的J2EE应用很有可能需要和外部系统交互。你的系统会需要读写文件,连接数据库来维护你的数据。这时如果数据交换中含有的多字节字符,你将面临一些棘手问题。 多数外部系统有它们自己的编码设置,比如说LDAP服务器使用UTF-8,Oracle数据库使用环境变量NLS_LANG指定编码。如果你在一个中文操作系统中安装Oracle,那么这个变量会被默认设置为ZHS16GBK,其使用GBK做字符编码。所以如果你J2EE应用的编码不与外部系统相同,则需要做如下转换: byte[] defaultBytes = original.getBytes(current_encoding); String newEncodingStr = new String(defaultBytes, old_encoding); 上面的代码显示了如何将一种编码的字符串转换为另一种编码。所以当以UTF-8编码的应用需要与以UTF-8编码的LDAP服务器交换用户信息时,你可以加入“original.getBytes("GBK")”以获得原始字符串的字节信息,并用“new String(defaultBytes, "UTF-8")”将字节信息转换为以UTF-8编码的新字符串,这能就能正确地传输和显示了。 原文地址: http://media.ccidnet.com/art/2623/20050926/340995_1.html