zoukankan      html  css  js  c++  java
  • Servlet--j2e中文乱码解决

    我们在写项目的时候经常会传递一些中文参数,但是j2e默认使用ISO-8859-1来编码和解码,所以很容易出现中文乱码问题。这里我做一个统一的整理,其实这里的中文乱码问题和上一篇的路径问题都是j2e经常遇见的很普遍的问题,不管你使用不使用框架都是很容易发生的,所以好好的整理一下还是很有必要的。
    • 具体有可能发生乱码的地方有:
    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中派生的。但是,实际上用8080端口的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>
    

  • 相关阅读:
    初识 Image,region,xld(1)
    Opencv 滤波<11>
    Opencv 掩模<10>
    事件
    Ubuntu16 安装Anaconda3+tensorflow cpu版
    Windows10:Opencv4.0+Opencv4.0.1_contrib编译
    Qt5连接Mysql环境配置
    Qt5显示中文字符
    如何为多个VLAN配置DHCP?
    二层网络架构,接入交换机和核心交换机
  • 原文地址:https://www.cnblogs.com/LinkinPark/p/5233001.html
Copyright © 2011-2022 走看看