技术开发 频道

专家讲述J2EE中的多字节字符的处理


服务器配置阶段

  在运行J2EE应用之前,一般会根据特定的需要对应用进行配置。在上一节中,我们发现不同的语言设置会导致文字字符串显示出问题。实际上配置存在于不同的层面,它们都可能会引发多字节字符问题。
 
操作系统层
 
  操作系统对语言的支持非常重要。前面提到服务器端对语言的支持会影响JVM默认的编码设置,而在客户端的语言支持(如字体)也能直接影响字符的显示,但这不是本文要讨论的重点。
 
J2EE应用服务器层
 
  大多数服务器都有一个基本服务器设置,可用来配置默认的字符编码处理方式。清单2就是Tomcat配置文件的一部分(位于$TOMCAT_HOME/conf/web.xml)。
 
清单2 web.xml
 
<servlet>       
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>           
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<strong>
<param-name>javaEncoding</param-name>
<param-value>>UTF8</param-value>
</strong>       
</init-param>       
<load-on-startup>3</load-on-startup> 
</servlet>
 
  Tomcat以参数javaEncoding来确定从JSP文件生成Java源文件的Java文件编码方式。这里的默认值是UTF-8,这意味着如果JSP文件中的中文字符以GBK编码保存,将会以UTF-8编码(浏览器端设置)显示,在这种情况下就可能会出问题。
 
JVM层
 
  大多数服务器都允许同时运行多个实例,且每个服务器实例都能有自己的JVM实例。此外,还可对每一个JVM实例分别设置。大多数服务器用本地设置来为每个实例定义默认的语言支持。
 
  此外,不同的服务器使用的JVM版本可能会不同,而不同的JDK版本支持的编码标准各异,所有这些都会导致迁移问题。例如Sun ONE应用服务器与Tomcat都支持J2SE 1.4,而有些服务器只支持到J2SE 1.3。J2SE 1.4支持Unicode 3.1,它具有许多早期版本所没有的新特性。
 
单个应用层
 
  每个部署在服务器上的应用在运行前都可以为其配置独立的编码设置,这就使得在同一个服务器实例上能够运行多个采用不同语言的应用。一些服务器用以下的字符编码设置为每个部署的应用指定其应使用的编码方式:
 
<locale-charset-info default-locale="en_US">     
</locale-charset-map locale="zh_CN" agent="Mozilla/4.77 [en] (Windows NT 5.0; U)" charset="GBK"></locale-charset-info>
 
  这种分层配置的目的是为了灵活性和可维护性。但不幸的是,当在服务器间迁移时这种做法就可能导致出问题,因为并非所有的服务器配置都遵循标准。比如说,如果在一个支持本地字符集设置的服务器上开发了应用,那么当把该应用迁移到另一个不支持这种编码设置的服务器上时就可能会遇到问题。
 
运行时阶段
 
  在运行过程中J2EE应用很可能会与其它外部系统通信。应用也许会读写文件,或者用数据库管理数据,有时候还可能用LDAP(轻量目录访问协议)服务器存储标识信息。在这些情况下,J2EE应用和外部系统之间需要进行数据交换。如果数据中带有象中文这样的多字节字符,就可能会遇到问题。
 
  大部分的外部系统都有他们自己的编码设置。例如LDAP服务器很可能使用UTF-8对字符编码;Oracle数据库系统用环境变量NLS_LANG来指定编码方式。如果Oracle是安装在中文操作系统上,该变量的默认设置为ZHS16GBK,也就是用GBK编码方式来存储中文字符。因此当J2EE应用的编码设置与外部系统不同时需要进行转码,通常用以下代码来完成这一工作:
 
byte[] defaultBytes =
original.getBytes(current_encoding);
String newEncodingStr =
new String(defaultBytes,
old_encoding);
 
  以上代码给出了如何将字符串从一种编码方式转换为另一种。例如你在LDAP服务器中用UTF-8编码存储了一个用户名(多字节字符),而在J2EE应用中用的却是GBK编码,因此当应用从LDAP服务器中取用户名时就可能被错误地编码。
 
  要解决这个问题,可以用original.getBytes("GBK")得到原始的字节,然后用new String(defaultBytes, "UTF-8")构造一个新字符串,这样就可以正确显示了。
 
客户端显示阶段
 
现在大多数J2EE应用都采用浏览器/服务器架构,以浏览器作为客户端。要在浏览器里正确显示多字节字符,需要注意以下几个方面:
 
浏览器语言支持:为能正确地显示多字节字符,浏览器及其所运行的操作系统应提供对特定语言的支持,比如字体和字码表。
 
浏览器编码设置:服务器返回的HTML头。
 
<meta http-equiv="content-type" content="text/html;charset=gb2312">
 
  向浏览器声明了该页面使用的编码方式,否则浏览器将使用默认编码设置或自动进行匹配。当然,用户也可以对页面的编码进行设定。如果页面没有声明,多字节字符就可能显示不正确,在这种情况下用户必须手工设定当前页面的编码方式。
 
HTTP POST编码
 
  用HTML页面的Form标签向服务器提交数据会使情况变得更为复杂。浏览器的编码方式取决于当前页面的编码设定,对Form标签也照此处理。这意味着如果ASCII格式的HTML页面用ISO-8859-1编码,那么用户在此页面中将不能提交中文字符。这是因为所有提交的数据都用ISO-8859-1编码,这将使中文字符丢失字节。所有的浏览器都遵守这个HTML标准。
 
HTTP GET编码
 
  URL链接中带有多字节字符会使事情复杂化,这种情况很常见,例如在链接里加入用户名或其它信息以便传给下一页。但RFC (因特网标准草案) 2396中并未明确规定URL中有非US-ASCII字符时的格式,不同的浏览器会采用它们自己的方式来编码URL中的多字节字符。以Mozila为例,通常是在HTTP请求发送前对URL编码。我们知道在URL编码过程中,首先根据某种编码方式(如UTF-8或GBK)将一个多字节字符转换成两个或更多的字节,然后每个字节用3个字符组成的字符串%xy来表示,其中xy是表示该字节的两个十六进制数。这方面的更多信息可参考HTML规范。不管怎样,URL编码所采用的编码方式取决于当前页面的编码方式。
 
我用下面这个gbk_test.jsp页面做演示:
 
清单3
 
gbk_test.jsp
 
<%@page contentType="text/html;charset=GBK"%>
<HTML>  
<BODY>     
<a href='/chartest/servlet/httpGetTest?name=王'>
<h1>Test for GBK encoded URL</h1></a>  
</BODY>
</HTML>
 
x738b是一个中文字符的转义值,这个中文字符就是我的姓。
 
  当鼠标移动到该链接上时,链接的地址就会在状态栏中显示出来,可以看到URL中嵌入了一个中文字符。点击页面中的链接,可以从地址栏中清楚地看到该字符已被URL编码。字符x738b被编码为%CD%F5,这是URL编码与GBK编码共同作用的结果。
 
  在服务器端,用request.getQueryString()方法取出查询字符串;为与查询字符串相比较,接下来的一行用另一种方法getParameter(String)来显示字符。把当前页面的编码方式由GBK改为UTF-8,再次点击页面中的链接,出现的结果为:x738b被编码为%E7%8E%8B,如图9所示,这是URL编码与UTF-8共同作用的结果。Microsoft的IE浏览器却以不同的方式处理多字节的URL编码。IE在HTTP请求发送前不对URL编码,URL编码方式取决于当前页面的编码方式。IE还有一个高级选项设置,可以强制浏览器总是以UTF-8编码方式发送URL请求。根据以上说明我们会面临一个问题:如果应用页面的URL链接中带有多字节字符,只要用GBK编码就能在Mozilla里正常使用;但如果用户的客户端是IE,且在设置中强制浏览器以UTF-8编码发送URL请求,那么在使用中就会遇到问题。
0
相关文章