1. 程式人生 > >Servlet--j2e中文亂碼解決

Servlet--j2e中文亂碼解決

body 抽象 getch imp 中文亂碼解決 整理 redirect 表單提交 rip

我們在寫項目的時候常常會傳遞一些中文參數,可是j2e默認使用ISO-8859-1來編碼和解碼,所以非常easy出現中文亂碼問題。

這裏我做一個統一的整理,事實上這裏的中文亂碼問題和上一篇的路徑問題都是j2e常常遇見的非常普遍的問題。無論你使用不使用框架都是非常easy發生的。所以好好的整理一下還是非常有必要的。

  • 詳細有可能發生亂碼的地方有:
1. 從數據庫到Java程序 byte——〉char
2. 從Java程序到數據庫 char——〉byte
3. 從文件到Java程序 byte——〉char
4. 從Java程序到文件 char——〉byte
5. 從流到Java程序byte——〉char
6. 從Java程序到流char——〉byte
7. 從Java程序到頁面顯示 char——〉byte
8. 從頁面form提交數據到Java程序byte——〉char

其它的臨時先無論,如今我們先來處理Servlet中的中文亂碼,也就是上面最後2點。

  • 首先必需要明確的,Tomcat的參數問題不管是GET或是POST方式都是用8859_1編碼的。
1,GET方式要看tomcat下源代碼。
protected static Locale defaultLocale = Locale.getDefault();貌似這裏的編碼使用的本地的編碼,可是我們細致看下
org.apache.tomcat.service.http. HttpRequestAdapter類
---- line=new String(buf, 0, count, Constants.CharacterEncoding.Default);
---- Constants.CharacterEncoding.Default=8859_1  
這段代碼不好跟蹤。千萬不要被一些假象迷惑住,HttpRequestAdapter是從RequestImpl中派生的。

可是。實際上用8080port的Server並沒有直接用到RequestImpl。而是用了HttpRequestAdapter來獲得queryString。

2,POST方式我們看下javax.servlet.http.HttpUtils的parsePostData方法。以下貼出源代碼。

public static Hashtable parsePostData(int len, ServletInputStream in)
	{
		if (len <= 0)
		{
			return new Hashtable();
		}
		if (in == null)
		{
			throw new IllegalArgumentException();
		}

		byte[] postedBytes = new byte[len];
		try
		{
			int offset = 0;
			do
			{
				int inputLen = in.read(postedBytes, offset, len - offset);
				if (inputLen <= 0)
				{
					String msg = lStrings.getString("err.io.short_read");
					throw new IllegalArgumentException(msg);
				}
				offset += inputLen;
			}
			while (len - offset > 0);
		}
		catch (IOException e)
		{
			throw new IllegalArgumentException(e.getMessage());
		}

		try
		{
			String postedBody = new String(postedBytes, 0, len, "8859_1");
			return parseQueryString(postedBody);
		}
		catch (UnsupportedEncodingException e)
		{
		}
		throw new IllegalArgumentException(e.getMessage());
	}

事實上研究這個tomcat的編碼源代碼沒啥意義,也就不用管了,記住就好,無論是GET還是POST,tomcat都是用8859_1來編碼的。
  • OK,如今開始整理中文亂碼的處理,解決Servlet中的亂碼問題。
1。表單提交
POST方式。在代碼第一行設置request的編碼格式就OK。

jsp頁面中一般都是設置過編碼格式的,一般都是UTF-8。所以我們在這裏request也設置用UTF-8解碼就OK。


req.setCharacterEncoding("UTF-8");
GET方式,上面的操作不生效,由於get方式提交參數丫的不是在header裏面是在url後面跟著的,僅僅能自己用String來轉換了。


String userName = new String(req.getParameter("userName").getBytes("ISO-8859-1"),"UTF-8");

2,超鏈接和重定向
比方:
<a href="/linkin/LinkinServlet?userName=林肯公園">GET方式傳參</a>
這2種情況和上面的用GET方式提交表單一樣,處理方式也一樣,這裏不做贅述了。

3,上面的情況都是屬於編碼級別的,一般的我們的項目上了生產上無論是GET方式還是POST方式,編碼格式這些都設置好了。最多是ajxa異步請求的時候添加過濾器編碼設置。

這裏針對tomcat本地開發說下:
假設是GET方式。在改動tomcat配置的地方加入一個屬性:URIEncoding,將該屬性值設置為UTF-8,就可以讓Tomcat(默認ISO-8859-1編碼)以UTF-8的編碼處理get請求。

<Connector port="8080"   protocol="HTTP/1.1"      connectionTimeout="20000"    redirectPort="8443" URIEncoding="UTF-8" />
假設是POST方式,就僅僅能添加編碼過濾器。沒使用框架的話用自己寫一個過濾器,建議將過濾的編碼寫成配置的,比方寫在<init-param>標簽中。假設使用了spring,則直接配置就OK。


<filter>
		<filter-name>encodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<!-- 編碼格式 -->
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<!-- 控制是否強制設置編碼,假設是true,無論request中有沒有指定編碼,這個過濾器設置編碼都會被觸發,假設是false,僅僅是在request中沒有設置編碼的時候被觸發設置上這裏的編碼 -->
		<init-param>
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
	<!-- 這裏是jsp中的編碼格式 都被設置為統一的了 -->
	<filter-mapping>
		<filter-name>encodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping> 

以下貼出這個類的源代碼:核心就是doFilterInternal()方法。

package org.springframework.web.filter;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class CharacterEncodingFilter extends OncePerRequestFilter
{

	/**
	 * @param request
	 * @param response
	 * @param filterChain
	 * @throws ServletException
	 * @throws IOException
	 * 個人不喜歡forceEncoding這樣的控制和別的控制一起寫的寫法,單獨拎出來寫成旗標多好
	 * 普通情況下。為了不沖掉自己的request中原有的編碼。建議forceEncoding配置成false
	 */
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException
	{
		//1,假設配置的encoding為空。不設置編碼
		//2,假設配置的encoding不為空,forceEncoding為true,無論request中有沒有自己的編碼都會設置編碼
		//3,假設配置的encoding不為空,forceEncoding為false,僅僅有在request沒有自己的編碼的時候才會設置編碼
		if (this.encoding != null && (this.forceEncoding || request.getCharacterEncoding() == null))
		{
			request.setCharacterEncoding(this.encoding);
			if (this.forceEncoding)
			{
				response.setCharacterEncoding(this.encoding);
			}
		}
		filterChain.doFilter(request, response);
	}

}

另外這裏也略微花點時間來說明一下,OncePerRequestFilter這個抽象過濾器非常好的實現了對每一個request僅僅運行一次過濾操作。假設有類似的需求能夠繼承該類並實現doFilterInternal方法來完畢。上面的CharacterEncodingFilter就是繼承這個抽象類的,以下貼出這個抽象類的源代碼,後面整理框架的時候我會做具體的整理的。

package org.springframework.web.filter;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public abstract class OncePerRequestFilter extends GenericFilterBean {

	
	public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED";


	//開始過濾,有點小技巧。實現了僅僅過濾一次的功能
	public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
			throw new ServletException("OncePerRequestFilter just supports HTTP requests");
		}
		HttpServletRequest httpRequest = (HttpServletRequest) request;
		HttpServletResponse httpResponse = (HttpServletResponse) response;

		String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
		if (request.getAttribute(alreadyFilteredAttributeName) != null || shouldNotFilter(httpRequest)) {//第2次進來
			// Proceed without invoking this filter...
			filterChain.doFilter(request, response);
		}
		else {//第一次進來
			// Do invoke this filter...
			request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
			try {
				doFilterInternal(httpRequest, httpResponse, filterChain);
			}
			finally {//最後清空alreadyFilteredAttributeName屬性
				// Remove the "already filtered" request attribute for this request.
				request.removeAttribute(alreadyFilteredAttributeName);
			}
		}
	}
	
	protected String getAlreadyFilteredAttributeName() {
		String name = getFilterName();
		if (name == null) {
			name = getClass().getName();
		}
		return name + ALREADY_FILTERED_SUFFIX;
	}
	
	protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
		return false;
	}
	
	//推遲到子類實現,真正的過濾的方法
	protected abstract void doFilterInternal(
			HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException;

}


4,向頁面傳參
後臺處理完邏輯後,跳轉到頁面上,假設頁面上中文出現亂碼,比方
resp.getWriter().write(req.getParameter("userName"));
在返回響應之前加入
response.setCharacterEncoding("UTF-8");
其有用上面那個設置編碼格式的方式不怎麽好,最好用以下這樣的的。
response.setContentType("text/html;charset=UTF-8");
response.setContentType("application/json;charset=gbk");


5,最後2點補充。


第一點,經常使用中文字符用utf-8編碼占用3個字節。用GBK、GB2312編碼的漢字占2個字節,嚴格地用iso8859-1無法表示漢字,僅僅能轉為問號。所以當我們傳遞的中文假設是基數的時候。即使我們正常編碼和轉碼了也會出現亂碼。在IE6版本號一下就會出現這樣的情況。解決的辦法就是在前臺頁面就編碼下中文。

java.net.URLEncoder.encode("林肯公園","UTF-8");
在後臺不須要轉碼,Servlet引擎已經幫我做好了。

某些情況下比方搜索引擎的搜索時。假設發送請求的是GET方式。瀏覽器上面的中文就會變成application/x-www-form-urlencoded MIME字符串。比方“%E6%E6”這樣的。Servlet引擎來解析這段字符串的時候自己主動會給我們轉會成漢字的。

轉碼的代碼例如以下:

URLDecoder.decode(req.getParameter("userName"), "UTF-8");

第二點。BASE64Encoder,BASE64Decoder編碼和解碼,這樣的編碼和解碼還會涉及算法的。了解下好了。

這2個類在API中查不到,由於JDK已經不推薦使用了。只是我個人認為還是挺好使的。

String name = new sun.misc.BASE64Encoder().encode("林肯公園".getBytes());// name:wda/z7mr1LA=
		System.out.println(new String((new sun.misc.BASE64Decoder()).decodeBuffer(name)));//林肯公園


OK。最後貼出自己寫的Servlet和jsp:

package linkin;

import java.io.IOException;
import java.net.URLDecoder;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author LinkinPark
 * @author 2015-7-10
 * @Descri 解決j2e中文亂碼問題
 */
public class LinkinServlet extends HttpServlet
{
	private static final long serialVersionUID = 1L;

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
	{
		//req.setCharacterEncoding("UTF-8");//解決POST方式
		//String userName = new String(req.getParameter("userName").getBytes("ISO-8859-1"),"UTF-8");//解決GET方式
		System.out.println(req.getParameter("userName"));
		System.out.println(URLDecoder.decode(req.getParameter("userName"), "UTF-8"));
		req.setAttribute("userName", "林肯公園");
		//resp.setCharacterEncoding("UTF-8");//解決向頁面傳參亂碼問題。建議使用以下這樣的
		resp.setContentType("text/html;charset=UTF-8");//解決向頁面傳參亂碼問題。建議使用這樣的
		resp.getWriter().write(req.getParameter("userName"));
		//req.getRequestDispatcher("/jsp/Linkin1.jsp").forward(req, resp);
		//resp.sendRedirect("/linkin/jsp/Linkin1.jsp");

		//以下使用base64來編碼和解碼
		String name = new sun.misc.BASE64Encoder().encode("林肯公園".getBytes());// name:wda/z7mr1LA=
		System.out.println(new String((new sun.misc.BASE64Decoder()).decodeBuffer(name)));//林肯公園
		
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
	{
		this.doGet(req, resp);
	}
	
	
	public static void main(String[] args) throws Exception
	{
		
	}

}
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
	String path = request.getContextPath();
	String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/";
%>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">

<title>Servlet中文亂碼</title>
<script type="text/javascript" src="/linkin/jsp/jquery-1.8.0.js"></script>
<script type="text/javascript">
var huhu = function(){
	var userName = $("#userName").val();
	$.ajax({
        url : "/linkin/LinkinServlet",
        type : "GET",
        async : true,
        data:{userName:userName},
        dataType : "json",
        success : function(a) {
        }
    });
}

</script>

</head>

<body>
	<form action="/linkin/LinkinServlet" method="GET">
		姓名:<input type="text" name="userName" id="userName" />
		<input type="button" value="提交" name="tijiao" onclick="huhu();"/>
		<a href="/linkin/LinkinServlet?userName=<%=java.net.URLEncoder.encode("林肯公園","UTF-8") %>林肯公園">GET方式傳參</a>
	</form>
</body>
</html>

Servlet--j2e中文亂碼解決