1. 程式人生 > >關於Servlet,JSP,HTML中文亂碼的問題

關於Servlet,JSP,HTML中文亂碼的問題

首先說明一點,以下的測試方法只有一個HttpServletRequest.forward,但是基於原理上的講解,其他亂碼問題應該也可以從中得到一些啟示。不敢保證百分百正確,但能提供一個大致的方向。

下面為測試入口servlet的程式碼,其中的getWriter被註釋掉,後面講其作用。

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        resp.setContentType("text/html;charset=utf-8");

        //PrintWriter out=resp.getWriter();

        resp.setHeader("Expires","0");
        resp.setHeader("Cache-Control","no-cache,no-store");
        resp.setHeader("Pragma","no-cache");

        req.getRequestDispatcher("/WEB-INF/jsp/in2.jsp").forward(req,resp);
    }

測試流程就是很簡單的從一個servlet轉到另一個jsp或者html上。

一.從servlet轉到jsp頁面

<1>直接轉到一個jsp

首先我們應該知道,jsp頁面實際上會轉換成一個servlet,至於存在那個地方,可以自行百度,不同的系統以及整合開發環境會使之存在不同的地方。下面為測試的第一個jsp檔案in2.jsp,很簡單,直接輸出一段話。

<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="UTF-8"%>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%
    out.print("我是in2.jsp檔案的內容");
    //out.print("引數為:"+request.getParameter("param"));
%>
</body>
</html>

其中的page裡面設定的幾個UTF-8編碼有必要講下它的作用範圍:

  • charset="UTF-8":它是設定的response的輸出編碼,其實設定的就是Content-type頭欄位裡面的編碼,是告訴瀏覽器以什麼方式解碼的。
  • pageEncoding="UTF-8":這個是指用什麼編碼格式開啟這個檔案,而不是以什麼格式儲存這個檔案,雖然想要得到正確的內容,開啟檔案的格式和檔案編碼的格式必須一致,但這個邏輯關係必須理清楚。
  • 其實還有另外一個屬性可以設定,那就是<head>標籤下的<meta>標籤可以設定文件的編碼格式,它作用的是html文件的內容,告訴瀏覽器以什麼編碼格式來解析文件。對於jsp而言,它會被content-type的charset給覆蓋掉。如果沒有設定content-type,指的是<%@ page%>裡設定的,生成的servlet會預設使用"text/html;charset=ISO-8859-1",<meta>標籤裡設定的無效,在jsp裡設定和不設定影響不到瀏覽器以什麼格式解碼。但在HTML裡就不一樣,因為HTML不生成servlet,所以可以指定瀏覽器以什麼格式解碼。但有一點及其重要,即便HTML以UTF-8格式儲存,標籤告訴瀏覽器以UTF-8格式解碼,但也可能產生亂碼,那是因為,伺服器在寫入HTML時,是以預設的格式寫入的,它沒有pageEncoding指定開啟寫入的格式,而預設格式為ANSI,不一定為UTF-8,所以就產生了亂碼!!!

下面的jsp轉換成一個servlet的流程必須清楚:

  • 將指定的jsp檔案以pageEncoding指定的編碼格式開啟,轉換成一個.java檔案
  • 類裝載器以utf-8的格式讀取.java檔案,轉換成一個.class檔案,在將這個.class檔案以Unicode編碼格式載入虛擬機器,現在就相當於一個servlet

上述in2.jsp檔案是UTF-8格式儲存的,接下來開啟它轉換成的servlet,片段程式碼如下,可以看到沒有亂碼

    out.write("\r\n");
      out.write("\r\n");
      out.write("<html>\r\n");
      out.write("<head>\r\n");
      out.write("    <title>Title</title>\r\n");
      out.write("</head>\r\n");
      out.write("<body>\r\n");

    out.print("我是in2.jsp檔案的內容");
    //out.print("引數為:"+request.getParameter("param"));

      out.write("\r\n");
      out.write("</body>\r\n");
      out.write("</html>\r\n");

瀏覽器端的輸出也是正常的

現在我們改動pageEncoding=ISO-8859-1

再次檢視jsp轉換成的servlet

    out.write("\r\n");
      out.write("\r\n");
      out.write("<html>\r\n");
      out.write("<head>\r\n");
      out.write("    <title>Title</title>\r\n");
      out.write("</head>\r\n");
      out.write("<body>\r\n");

    out.print("ææ¯in2.jspæ件çå容");
    //out.print("åæ°ä¸º:"+request.getParameter("param"));

      out.write("\r\n");
      out.write("</body>\r\n");
      out.write("</html>\r\n");

可以看到已經亂碼了,這就是以ISO-8859-1編碼開啟UTF-8編碼檔案時產生的亂碼,網頁輸出端也是如此

因此,可以總結出,如果要保證不亂碼,必須以檔案編碼的格式開啟檔案,而且還得指定瀏覽器以同樣的編碼格式解碼。這兒並未測試response設定編碼格式,但得注意這點!

<2>現在改變一下測試方式,從一個servlet轉到test.jsp,然後test.jsp轉到in2.jsp

test.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>測試jsp</title>
</head>
<body>
<jsp:forward page="in2.jsp">
    <jsp:param name="param" value="data資料"/>
</jsp:forward>
</body>
</html>

in2.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="ISO-8859-1"%>
<html>
<head>
    <title>Title</title>
</head>
<body>
<%
    out.print("我是in2.jsp檔案的內容");
    out.print("引數為:"+request.getParameter("param"));
%>
</body>
</html>

入口servlet

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

//        resp.setContentType("text/html;charset=utf-8");
//        PrintWriter out=resp.getWriter();

        resp.setHeader("Expires","0");
        resp.setHeader("Cache-Control","no-cache,no-store");
        resp.setHeader("Pragma","no-cache");

        req.getRequestDispatcher("/WEB-INF/jsp/test.jsp").forward(req,resp);
    }

網頁端得輸出為以下情況:

in2.jsp檔案的內容沒問題,test.jsp中設定的引數漢字亂碼,這個也可以從test_jsp.java裡面看到原因,是因為這個"data資料"是更據request的編碼格式來寫入的,預設為ISO-8859-1,將其改變為utf-8則正常顯示。在設定引數之前改變編碼,不要在亂碼之後改變編碼,那時沒用了,可以在jsp裡面改變,也可以在入口servlet改變,如req.setCharacterEncoding("utf-8");。

二.從一個servlet轉到一個靜態HTML

靜態HTML檔案如下in1.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="content-type" content="text/html" charset="UTF-8">
    <title>Title</title>
</head>
<body>
這是in1.html裡面的內容
</body>
</html>

入口servlet的程式碼如下:

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

        resp.setContentType("text/html;charset=utf-8");
        PrintWriter out=resp.getWriter();

        resp.setHeader("Expires","0");
        resp.setHeader("Cache-Control","no-cache,no-store");
        resp.setHeader("Pragma","no-cache");
        req.setCharacterEncoding("utf-8");
        req.getRequestDispatcher("/WEB-INF/jsp/in1.html").forward(req,resp);
    }

結果為如下,亂碼了,歸根揭底其實也是pageEncoding的原因

因為HTML檔案無法設定pageEncoding,也不會生成servlet,所以我也不能確定是不是pageEncoding的原因,因此作出假設,轉發到HTML後,HTML的開啟方式為以下2種情況。

  1. 是以UTF-8的格式開啟
  2. 不是以UTF-8的格式開啟,這個格式我等下說明

以notepad++開啟這個in1.html,並且將編碼格式設定為ANSI,則結果如下,剛好和瀏覽器輸出一致

因此我猜測是以ANSI的格式開啟HTML,這個格式會因作業系統和地區而已,中國WINDOWS為GBK格式

下面我另存為一個in2.html格式儲存為ANSI

並將servlet的轉發到in2.html,輸出正常

因此,我認為一個HTML檔案在整合開發建立時是UTF-8的格式,這個格式在IDE上應該可以設定,但在開啟時是更具預設編碼格式開啟的(即ANSI),因此會產生亂碼,當然,這只是找到了原因。而解決的辦法就是將這個HTML以我們想要的格式開啟,即UTF-8,但又不能設定pageEncoding,因此我們可以將HTML當作JSP處理,靜態HTML是可以轉成JSP的。

這種途徑可以配置XML檔案,新增如下XML語句

    <jsp-config>
        <jsp-property-group>
            <url-pattern>*.html</url-pattern>
            <page-encoding>UTF-8</page-encoding>
        </jsp-property-group>
    </jsp-config>

意思是,任何以html結尾的URL請求的資源,都已UTF-8格式開啟,因此在次轉到in1.html則正常了。如果配置了XML就不能在訪問in2.html,會亂碼,因為in2.html的編碼格式為ANSI,以UTF-8的格式開啟會亂碼。

關於最開始說的PrintWriter out=resp.getWriter的作用關於另一個知識點,對於一切沒有在XML中配置的servlet,都是採用預設servlet訪問,關於預設servlet,可以百度檢視更多。如上面測試時,如果在轉發之前沒有使用getWriter,則預設servlet使用的是位元組流輸出,如果使用了getwriter則使用字元流輸出,位元組流同理。關於content-type頭欄位的charset編碼格式和位元組流字元流之間又會產生多種情況,如使用字元流,但不指定charset,則預設的格式為ISO-8859-1,用來輸出中文則會亂碼;使用位元組流時,不論是否設定為utf-8,都不會產生亂碼,按理位元組流使用utf-8輸出中文會亂碼,我沒有去看這個預設servlet的原始碼,只能猜測使用位元組流時都採用ISO-8859-1來輸出。