很奇怪測試了多個應用服務器,只有Tomcat出現了亂碼問題。讓我們來分析一下原因,測試環境是Tomcat5.5.27,字符集編碼統一為UTF-8。
1.頁面靜態內容亂碼(非動態生成內容亂碼)
這一般是<%@ page pageEncoding="UTF-8" %>設置的問題,建議在每個頁面上都加上pageEncoding設定,讓應用服務器能正確把JSP文件按照設定的編碼轉換為Java文件,只要這個pageEncoding設置正確就可以避免靜態內容的亂碼。有人可能會說我沒有設置也沒有亂碼,那是因為應用服務器還可以讀取<%@ page contentType="text/html; charset=UTF-8" %>中的charset作為備選方案,雖然這是JSP規范中要求的,但是難保有的容器沒有實現或實現有BUG,所以有時候在某個應用服務器下(如Tomcat)不設置pageEncoding也可以,但是同樣的頁面拿到別的應用服務器下就不能保證不出現亂碼。
2.動態生成內容亂碼
新下載的Tomcat沒有經過任何特殊的設置,無論是GET和POST都出現亂碼。首先設置HTTP Connector(server.xml中監聽8080端口的那個Connector),加上URIEncoding="UTF-8",消除了GET亂碼,再在JSP頁面中第一句加入<% request.setCharacterEncoding("UTF-8"); %>,消除了POST亂碼。
通過上面兩個設置我們發現,URIEncoding控制的是GET字符集編碼,Request的CharacterEncoding控制的是POST字符集編碼。
如果沒有上面那句<% request.setCharacterEncoding("UTF-8"); %>,在頁面起始加入<%= request.getCharacterEncoding() %>,在Tomcat下我們發現輸出null,在其他服務器下卻輸出UTF-8。這就是為什么在Tomcat下應該正確設置Request的CharacterEncoding的原因。
上面提到的<%@ page contentType="text/html; charset=UTF-8" %>,除了聲明返回給客戶端的流是text/html外,同時設置了Response的CharacterEncoding,即相當于執行了Response.setCharacterEncoding("UTF-8")這段代碼。它保證了服務器端生成的動態內容到達客戶端也不會亂碼。
但有一種情況下也不會出現亂碼,就是如下例這種情況,前提是沒有設置Request的CharacterEncoding:
1 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
2 response.getWriter().write(request.getParameter("xxxxx"));
3 }
這種情況下提交過來的表單數據其實是ISO-8859-1的編碼,而返回給客戶端又沒有<%@ page contentType="text/html; charset=UTF-8" %>的設置,所以還是ISO-8859-1的編碼,但是為什么沒有亂碼呢?其實已經亂碼了,如果在第2行下斷點的話,會發現request.getParameter("xxxxx")的返回值就是亂碼。可以用一句Java代碼來解釋為什么客戶端顯示結果沒有亂碼,如下:
1 System.out.println(new String("你好,世界".getBytes("ISO-8859-1"), "ISO-8859-1");
很奇怪這句代碼,明明是中文,應該用GB2312或GBK之類的字符集編碼來getBytes,卻用了ISO-8859-1,事實證明,這種互逆操作對字符串本身沒有任何影響,只要getBytes和new String的時候字符集編碼是一致的就不會引起亂碼。
上面這句代碼正好說明了數據從客戶端POST到服務器端時是ISO-8859-1編碼,然后從服務器端寫回到客戶端還是ISO-8859-1編碼,所以就沒有造成亂碼,如果這里不是直接寫回到客戶端,而是forward到另一個JSP頁面,而這個頁面恰好使用了<%@ page contentType="text/html; charset=UTF-8" %>來設置Response的CharacterEncoding,那么在頁面中輸出xxxxx還會產生亂碼,同樣用一句Java代碼來解釋,如下:
1 System.out.println(new String("你好,世界".getBytes("ISO-8859-1"), "UTF-8"));
所以,最后結論是如果想POST到服務器端不亂碼就要設置Request的CharacterEncoding,寫回到客戶端不亂碼就要設置Response的CharacterEncoding,若是JSP頁面要設置<%@ page contentType="text/html; charset=UTF-8" %>。
3.AJAX亂碼問題(不借助任何JS框架,像Prototype之類的框架會對GET請求的queryString自動應用encodeURIComponent()編碼)
GET請求時,需要對queryString使用encodeURIComponent()編碼之后再提交到服務器。這是XMLHttpRequest規范所要求的。
POST請求時,不需要使用encodeURIComponent()。
通過對應用程序下斷點發現,GET請求和POST請求的數據發送到服務器端都是正常的沒有亂碼,但是服務器端生成的動態內容寫回客戶端卻是亂碼,說明Response的CharacterEncoding設置錯誤,反過來我們再想一下,我們根本就沒有設置過Response的CharacterEncoding,為什么呢?因為我們是以AJAX的方式提交表單,返回后不像JSP頁面那樣有<%@ page contentType="text/html; charset=UTF-8" %>來設置Response的CharacterEncoding,所以就會出錯。
綜合上述,解決的辦法就是各大網站提出的通用解決方案Filter,如果你的應用沒有用到AJAX,只設置Request的CharacterEncoding即可,否則Response的CharacterEncoding也要設置。下面是一個Filter的示例,只引用doFilter方法來說明問題:
1 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
2 request.setCharacterEncoding("UTF-8");
3 response.setCharacterEncoding("UTF-8");
4 chain.doFilter(request, response);
5 }