【转】解决jsp参数传递乱码的问题

时间:2023-01-21 17:58:31

解决jsp参数传递乱码的问题

计算机生于美国,英语是他的母语,而英语以外的其它语言对他来说都是外语。他跟我们一样,不管外语掌握到什么程度,也不会像母语那样使用得那么好,时常也会出一些“拼写错误”问题。

乱码的出现根本原因在于编码和解码使用了不同的编码方案。比如用GBK编码的文件,用UTF-8去解码结果肯定都是火星文。所以要解决这个问题,中心思想就在于使用统一的编码方案。

jsp页面间的参数传递有以下几种方式:1、表单(form)的提交。2、直接使用URL后接参数的形式(超级链接)。3、如果两个jsp页面在两个不同的窗口中,并且这两个窗口是父子的关系,子窗口中的jsp也可以使用javascript和DOM(window.opener.XXX.value)来取得父窗口中的jsp的输入元素的值。下面就前两种方式中出现的乱码问题做一下剖析。

1、表单(form)的提交实现参数页面间的传递

在介绍表单传递参数的内容之前,先来了解一些预备知识。表单的提交方式和请求报文中对汉字的处理。

表单的提交方式:

通常使用的表单的提交方式主要是:post和get两种。两者的区别在于:post方式是把数据内容放在请求的数据正文部分,没有长度的限制;get方式则是把数据内容直接跟在请求的头部的URL后面,有长度的限制。下面是同一个页面两种方式的请求报许文。

Requesttest.jsp代码  
<%@ page language="java" contentType="text/html; charset=UTF-8"  
    pageEncoding="UTF-8"%>   
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">   
<html>   
<head>   
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">   
<title>Insert title here</title>   
</head>   
<body>   
<%-- post方式提交表单 --%>   
<form action="http://localhost:8888/EncodingTest/requestresult.jsp" method="post">   
    UserName:<input type="text" name="username"/>   
    Password:<input type="password" name="password"/>   
    <input type="submit" value="Submit">   
</form>   
</body>   
</html>  
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <%-- post方式提交表单 --%> <form action="http://localhost:8888/EncodingTestb/requestresult.jsp" method="post"> UserName:<input type="text" name="username"/> Password:<input type="password" name="password"/> <input type="submit" value="Submit"> </form> </body> </html> 
      在上面的请求页面的username输入框里输入的是“世界杯”三个汉字,password输入框中输入"123"后按下Submit按钮提交请求。截获到的请求报文如下:

Post方式的请求报文代码  
POST /EncodingTest/requestresult.jsp HTTP/1.1  
Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*   
Referer: http://localhost:8080/TomcatJndiTest/requesttest.jsp   
Accept-Language: zh-cn   
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; CIBA; aff-kingsoft-ciba; .NET CLR 2.0.50727)   
Content-Type: application/x-www-form-urlencoded   
Accept-Encoding: gzip, deflate   
Host: localhost:8888  
Content-Length: 49  
Connection: Keep-Alive   
Cache-Control: no-cache   
  
username=%E4%B8%96%E7%95%8C%E6%9D%AF&password=123  
POST /EncodingTest/requestresult.jsp HTTP/1.1 Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */* Referer: http://localhost:8080/TomcatJndiTest/requesttest.jsp Accept-Language: zh-cn User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; CIBA; aff-kingsoft-ciba; .NET CLR 2.0.50727) Content-Type: application/x-www-form-urlencoded Accept-Encoding: gzip, deflate Host: localhost:8888 Content-Length: 49 Connection: Keep-Alive Cache-Control: no-cache username=%E4%B8%96%E7%95%8C%E6%9D%AF&password=123 
      以上报文内容,可以看出post方式的请求报文是有专门的数据部的。,

下面的同一请求页面的get提交方式的请求报文:

Get方式的请求报文代码  
GET /EncodingTest/requestresult.jsp?username=%E4%B8%96%E7%95%8C%E6%9D%AF&password=123 HTTP/1.1  
Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*   
Referer: http://localhost:8080/TomcatJndiTest/requesttest.jsp   
Accept-Language: zh-cn   
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; CIBA; aff-kingsoft-ciba; .NET CLR 2.0.50727)   
Accept-Encoding: gzip, deflate   
Host: localhost:8888  
Connection: Keep-Alive  
GET /EncodingTest/requestresult.jsp?username=%E4%B8%96%E7%95%8C%E6%9D%AF&password=123 HTTP/1.1 Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */* Referer: http://localhost:8080/TomcatJndiTest/requesttest.jsp Accept-Language: zh-cn User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; CIBA; aff-kingsoft-ciba; .NET CLR 2.0.50727) Accept-Encoding: gzip, deflate Host: localhost:8888 Connection: Keep-Alive 
     以上报文内容,可以看出get方式的请求报文没有专门的数据部,数据是直接跟在url的后面。

请求报文中对汉字的处理:

从上面两种报文可以看出页面上输入的“世界杯”三个汉字被替换成了"%E4%B8%96%E7%95%8C%E6%9D%AF”这样一个字符串,然后发给服务器的。看到这,可能会有两个问题:问题一、这个字符串是什么?问题二、为什么要做这样的替换?

这个字符串是“世界杯”这三个汉字对应的"UTF-8”编码"E4B896E7958CE69DAF"在每个字节前追加一个"%"后形成的。至于为什么要做这样的转化,我的理解是:因为请求报文会以"ISO-8859-1"的编码方式编码后,通过网络流的方式传送到服务器端。"ISO-8859-1"仅支持数字、英文字母和一些特殊字符,所以像汉字等这样的字符"ISO-8859-1"是不认识的。所以就必须先给这些"ISO-8859-1"不支持的字符做个“整形”手术。这样才能正确的将页面上的信息传送到服务器端。

这时可能又会有另外一个问题:上面的例子中为什么会选用"UTF-8"编码,其它的编码方案可以吗?答案是可以的。在jsp页面代码的头部有这样一段代码"<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>"其中charset的值就是浏览器在提交请求报文前,对请求报文做“整形”手术时用的字符集,同是也是浏览器解释服务器的响应页面时的字符集。

在了解了以上内容后,开始剖析表单方式传递参数的乱码问题。

以上例为例,点击"Submit"按钮后,浏览器将做完“整形”手术后的请求报文发送给WEB服务器上的Servlet容器,容器在收到这个请求报文后,会解析这个请求报文并用这个报文的信息生成一个HttpServletRequest对象,然后将这个HttpServletRequest对象传给这个页面所要请求的jsp或Servlet(上例中为"requestresult.jsp")。在这个被请求的jsp或Servlet(上例中为"requestresult.jsp")中,使用HttpServletRequest对象的getParameter("")方法来取得上一页面传来的参数。默认情况下,这一方法使用的是"ISO-8859-1"来解码,所以对于英文或数字的参数值自然能正确取得,但对于汉字这样的字符是解不出来的,因为那几个汉字曾经做过“整形”手术,已经认不出来了。要想再把它们认出来,那就得要把手术的主刀医生找到,然后再做一次“还原”手术。下面提供的几个方案,可用于不同的情况。

方案一代码  
<%String str = new String(request.getParameter("username").getBytes("ISO-8859-1"),"utf-8"); %>   
Username:<%=str %>  
<%String str = new String(request.getParameter("username").getBytes("ISO-8859-1"),"utf-8"); %> Username:<%=str %> 
      既然request.getParameter("username")默认情况下返回的字符串是用"ISO-8859-1"解出来的,那就先把这个不可辨认的字符串再用"ISO-8859-1"来打散,也就是:request.getParameter("username").getBytes("ISO-8859-1")。最后再用跟你的页面的charset一致的字符集来重组这个字符串:new String(request.getParameter("username").getBytes("ISO-8859-1"),"utf-8")。这样就能见到它的庐山真面目了。

 

      方案一是一种比较万能的方法,不管是post还是get都适用,但可以看出它的缺点是:对于每个可能出现汉字的参数都要显示的做这么一段处理。一个两个还行,要是很多的话,那就应该考虑一下是不是可以选用下一种方案。

  

方案二代码  
<%request.setCharacterEncoding("UTF-8"); %>  
<%request.setCharacterEncoding("UTF-8"); %> 
      方案二是在页面的最开始或者是在该页面中使用的第一个request.getParameter("")方法之前加上上述一段代码,它的作用是用作为参数传入的编码集去覆盖request对象中的默认的"ISO-8859-1"编码集。这样request.getParameter("")方法就会用新的编码集去解码,因为"UTF-8"支持中文,所以作为参数传过来的“世界杯”三个汉字就能正确的接收到了。但关于request.setCharacterEncoding("")方法,API文档中有如下的说明:

 

      Overrides the name of the character encoding used in the body of this request. This method must be called prior to reading request parameters or reading input using getReader(). Otherwise, it has no effectb.

 

      所以方案二只对post方式提交的请求有效,因为参数都在request的body区。而对get方式提交的请求则是无效的,这时你会发现同样的做法但显示的还是乱码。所以你的请求要是是以get方式提交的话,那你还是乖乖的选用方案一吧!

从上面的叙述可以知道,方案二需要在每个页面的前头加上<%request.setCharacterEncoding("UTF-8"); %>这段代码,这样做是不是也挺累的,所以我们想到了使用过滤器来帮助我们做这件事儿,那就清爽、简单多了。

Encodingfilter代码  
public class EncodingFilter implements Filter {   
       
    private String charset;   
    @Override   
    public void destroy() {   
        // TODO Auto-generated method stub   
    }   
  
    @Override   
    public void doFilter(ServletRequest request, ServletResponse response,   
            FilterChain chain) throws IOException, ServletException {   
        //用init方法取得的charset覆盖被拦截下来的request对象的charset   
        request.setCharacterEncoding(this.charset);   
        //将请求移交给下一下过滤器,如果还有的情况下。   
        chain.doFilter(request, response);   
    }   
  
    @Override   
    public void init(FilterConfig config) throws ServletException {   
        //从web.xml中的filter的配制信息中取得字符集   
        this.charset = config.getInitParameter("charset");   
    }   
}  
public class EncodingFilter implements Filter { private String charset; @Override public void destroy() { // TODO Auto-generated method stub } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { //用init方法取得的charset覆盖被拦截下来的request对象的charset request.setCharacterEncoding(this.charset); //将请求移交给下一下过滤器,如果还有的情况下。 chain.doFilter(request, response); } @Override public void init(FilterConfig config) throws ServletException { //从web.xml中的filter的配制信息中取得字符集 this.charset = config.getInitParameter("charset"); } } 
        要想这个过滤器生效,还得到web.xml里加入下面的配制信息。

Web.xml代码  
<filter>   
   <filter-name>EncodingFilter</filter-name>   
   <filter-class>cn.eric.encodingtest.filter.EncodingFilter</filter-class>   
   <init-param>   
       <param-name>charset</param-name>   
       <param-value>UTF-8</param-value>   
   </init-param>   
</filter>   
<filter-mapping>   
   <filter-name>EncodingFilter</filter-name>   
   <url-pattern>/*</url-pattern>   
</filter-mapping>  
<filter> <filter-name>EncodingFilter</filter-name> <filter-class>cn.eric.encodingtest.filter.EncodingFilter</filter-class> <init-param> <param-name>charset</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>EncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

2、直接使用URL后接参数的形式(超级链接)。

有些时候可能会遇到通过一个超级链接来把参数传到下一个页面,而刚好这个参数的值有可能会出现中文的情况。就像下面这样:

<a href="./jstlresult.jsp?content=世界杯">Go South Africa

跟form提交有些不同的是:当你点击这个超级链接后在浏览器的地址栏里看到的是http://localhost:8080/TomcatJndiTest/jstlresult.jsp?content=世界杯,而不是http://localhost:8080/TomcatJndiTest/jstlresult.jsp?content=%E4%B8%96%E7%95%8C%E6%9D%AF

这里浏览器并没有帮我们把这个转化工作搞定,所以这里要自己动手,丰衣足食了。做法如下:

<a href="./jstlresult.jsp?content=<%=java.net.URLEncoder.encode("世界杯","utf-8") %>">Go South Africa

这样的话在第二个页面就能使用<%String str = new String(request.getParameter("content").getBytes("ISO-8859-1"),"utf-8"); %>的方法来正确的得到这个参数值了。

总结一下:

1、post提交的方式:使用过滤器,将到达页面前的request对象中的字符编码设定成跟你页面统一的编码。

2、get提交的方式:<%String str = new String(request.getParameter("content").getBytes("ISO-8859-1"),"utf-8"); %>这样的字符串重组的方法。

3、超级链接方式:先将链接url中的汉字用java.net.URLEncoder.encode("paramValue","charset")方法处理一下,下面的做法参照2。