JSP中文亂碼問題終極解決方案(上)

書呆子Rico發表於2017-02-27

摘要:

  本文首先介紹了一個JSP的原始檔執行過程,即需要經過三個階段,兩次編碼,才能完成一次完整的輸出。特別需要注意的是,在這個過程中,編碼問題貫穿始終。我們知道在JSP/Servlet中,主要有以下四種方式可以設定編碼,即 pageEncoding、contentType、request.setCharacterEncoding 和 response.setCharacterEncoding,在本文中,我們就這四種方式進行深入的介紹和總結。


一. JSP的執行過程與編碼設定概述

  在JSP/Servlet中,主要有以下四種方式可以設定編碼,其中前兩個只能應用於JSP中,而後兩個可以用於 JSP 和 Servlet 中。

  • pageEncoding=”UTF-8”;
  • contentType=”text/html;charset=UTF-8”;
  • request.setCharacterEncoding(“UTF-8”) ;
  • response.setCharacterEncoding(“UTF-8”)。

  事實上,一個JSP的原始檔需要經過三個階段,兩次編碼,才能完成一次完整的輸出,這三個階段是:

  第一階段:轉譯(.jsp -> .java;pageEncoding -> UTF-8)。將jsp編譯成Servlet(.java)檔案,用到的指令是pageEncoding。在編譯過程中,根據pageEncoding=“XXX”的指示,找到編碼的規則為“XXX”,然後伺服器將JSP檔案編譯成.java檔案時會根據pageEncoding的設定讀取jsp,結果是由指定的編碼方案翻譯成統一的UTF-8編碼的JAVA原始碼(即.java)。

  第二階段:編譯(.java -> .class;UTF-8 -> UTF-8)。從Servlet檔案(.java)到Java位元組碼檔案(.class),從UTF-8到UTF-8。在這一階段中,不論JSP編寫時候用的是什麼編碼方案,經過這個階段的結果全部是UTF-8的encoding的java原始碼。JAVAC用UTF-8的encoding讀取java原始碼,編譯成UTF-8編碼的二進位制碼(即.class),這是JVM對常數字串在二進位制碼(Java encoding)內表達的規範。這一過程是由JVM的內在規範決定的,不受外界控制。

  第三階段:編譯(UTF-8 -> contentType)。從伺服器到瀏覽器,這在一過程中用到的指令是contentType。伺服器載入和執行由第二階段生成出來JAVA二進位制碼,輸出的結果,也就是在客戶端可見到的結果,在這次輸出過程中,由contentType屬性中的charset來指定,將UTF8形式的二進位制碼以charset的編碼形式來輸出。如果沒有人為設定,則預設的是ISO-8859-1的形式。

  特別需要注意的是,pageEncoding 的預設值是 “ISO-8859-1”, contentType 的預設值是 “text/html;ISO-8859-1”。

  Ps: 第一、三兩個階段的轉碼個人感覺聯想到Sting轉碼更容易理解些,例如 :new String(name.getBytes(“ISO-8859-1”), “utf-8”)。


二. pageEncoding=”UTF-8”

  pageEncoding=”UTF-8” 的作用是設定JSP編譯成Servlet時使用的編碼。通常,在JSP內部定義的字串(直接在JSP中定義,而不是從瀏覽器提交的資料)出現亂碼時,很多都是由於該引數設定錯誤引起的。例如,你的 JSP檔案中含有中文字元,而在JSP中卻指定pageEncoding=”iso-8859-1”,就會導致中文字元顯示異常。看下面的例子:

<%@ page language="java" pageEncoding="iso-8859-1" import="java.util.*" %>

<html>
  <head>
    <title>哈哈</title>
  </head>
  <body>
     中文 <br>
  </body>
</html>

在其編譯為Servlet後,其原始碼(片段)如下所示:

public void _jspService(HttpServletRequest request, HttpServletResponse response)
        throws java.io.IOException, ServletException {

      // ...

      out.write("<html>\r\n");
      out.write("  <head>\r\n");
      out.write("    <title>哈哈</title>\r\n");
      out.write("  </head>\r\n");
      out.write("  <body>\r\n");
      out.write("   \t 中文 <br>\r\n");
      out.write("  </body>\r\n");
      out.write("</html>\r\n");

      // ...

訪問該頁面,頁面顯示如下:

               pageEncoding.png-13.9kB

  我們可以看到,由於pageEncoding被指定為”iso-8859-1”,導致其在由伺服器將JSP檔案編譯成.java檔案過程中,在使用 “iso-8859-1” 讀取jsp並翻譯成統一的UTF-8編碼的JAVA原始碼時,所有的中文字元被轉成亂碼,並使得其呈現給使用者的響應也包含亂碼。特別地,該屬性還有一個功能,就是在JSP中不指定contentType引數,也不使用response.setCharacterEncoding方法時,指定對伺服器響應的內容進行編碼。


二. contentType=”text/html;charset=UTF-8”

  contentType=”text/html;charset=UTF-8” 的作用是將上述第二階段所生成的UTF8形式的二進位制碼以charset的編碼形式來輸出到客戶端,如果設定不當的話,會出現亂碼。看下面的例子:

<%@ page language="java" contentType="text/html;iso-8859-1" import="java.util.*" 
    pageEncoding="utf-8"%>

<html>
  <head>
    <title>哇哈哈</title>
  </head>
  <body>
     哇哈哈 <br>
  </body>
</html>

在其編譯為Servlet後,其原始碼(片段)如下所示:

public void _jspService(HttpServletRequest request, HttpServletResponse response)
        throws java.io.IOException, ServletException {

      // ...

      out.write("<html>\r\n");
      out.write("  <head>\r\n");
      out.write("    <title>哇哈哈</title>\r\n");
      out.write("  </head>\r\n");
      out.write("  <body>\r\n");
      out.write("   \t 哇哈哈 <br>\r\n");
      out.write("  </body>\r\n");
      out.write("</html>\r\n");

      // ...

訪問該頁面,頁面顯示如下:

               contentType.png-13.1kB


三. request.setCharacterEncoding(“UTF-8”)

  request.setCharacterEncoding(“UTF-8”)用來指定對瀏覽器傳送來的資料以特定的字符集進行重新編碼,常用於對 POST 請求引數進行解碼。具體見我的博文 《JSP中文亂碼問題終極解決方案(下)》中 “POST 請求的請求引數為中文情形” 一節。


四. response.setCharacterEncoding(“UTF-8”)

  response.setCharacterEncoding(“UTF-8”)的作用是:在伺服器將響應返回到瀏覽器前,對響應使用指定字符集進行重新編碼。一旦使用了該種方式,即使該響應頁面指定了具體的 contentType,也將失效。看下面的例子:

<%@ page language="java" contentType="text/html;iso-8859-1" import="java.util.*" 
    pageEncoding="utf-8"%>

<html>
  <head>
    <title>哇哈哈</title>
  </head>
  <body>
     哇哈哈 <br>
  </body>
</html>

在其編譯為Servlet後,其原始碼(片段)如下所示:

public void _jspService(HttpServletRequest request, HttpServletResponse response)
        throws java.io.IOException, ServletException {

      // ...

      out.write("<html>\r\n");
      out.write("  <head>\r\n");
      out.write("    <title>哇哈哈</title>\r\n");
      out.write("  </head>\r\n");
      out.write("  <body>\r\n");
      out.write("   \t 哇哈哈 <br>\r\n");
      out.write("  </body>\r\n");
      out.write("</html>\r\n");

      // ...

訪問該頁面,頁面顯示如下:

               setContentType.png-13.6kB


五. 四種編碼設定方式之間的相互影響以及作用的優先順序

  根據上文內容,我們得出以下三點:

  • 在指定JSP編譯成Servlet時使用的編碼時,優先順序為: pageEncoding=”UTF-8” > contentType=”text/html;charset=UTF-8”

  • 在指定伺服器對響應內容的編碼時,優先順序為:response.setCharacterEncoding(“UTF-8”) > contentType=”text/html;charset=UTF-8” > pageEncoding=”UTF-8”

  • request.setCharacterEncoding(“UTF-8”) 只用來指定對瀏覽器傳送來的請求資料的解碼方式。


  更多關於 JSP技術的細節見我的其他兩篇部落格: 《Java Web基礎 — Jsp 綜述(上)》《Java Web基礎 — Jsp 綜述(下)》

  更多關於 JSP中文亂碼問題的解決方案見我的另一篇部落格:《 JSP中文亂碼問題終極解決方案(下)》


引用

關於JSP頁面中的pageEncoding和contentType兩種屬性的區別
JSP裡ContentType ,charset和pageEncoding的理解與區別

相關文章