關於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種情況。
- 是以UTF-8的格式開啟
- 不是以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來輸出。